Compare commits

..

14 Commits
v4.0.2 ... v3.2

Author SHA1 Message Date
Bai
34c3147264 fix: 修复删除组织时组织根节点未被删除的问题 2023-05-16 16:37:08 +08:00
老广
1cf57c822d Merge pull request #10417 from jumpserver/pr@v3.2@fix_categorytree1
fix: 修复资产类型树循环显示的问题
2023-05-10 11:07:18 +08:00
Bai
edfac357b9 fix: 修复资产类型树循环显示的问题 2023-05-10 02:53:20 +00:00
feng
2e5dfd8bab fix: /prometheus/metrics/ api 500 2023-05-08 14:51:18 +08:00
Bai
53da79ae46 perf: 优化迁移后的 Redis 数据库平台从 Redis6+ 修改为 Redis6 2023-05-04 17:31:57 +08:00
Bai
e258772242 fix: 修复迁移应用时(组织下只有根节点,同步后的应用资产没有设置节点的问题) 2023-05-04 16:36:52 +08:00
Bai
0a5bf9cf03 fix: 修复迁移redis资产账号丢失的问题(系统用户用户名为空字符串) 2023-05-04 15:44:08 +08:00
Eric
ac7c865b67 fix: 修复旧 ssh 私钥,解析失败的问题 2023-04-27 17:49:29 +08:00
feng
ff6a8fa4d7 perf: 开源 acl去除 review 2023-04-21 18:41:26 +08:00
ibuler
802d6136d6 perf: 账号模版 protocols 过滤 2023-04-21 17:11:46 +08:00
ibuler
7e8bb9752c perf: 优化自定义类型的冲突 2023-04-21 15:20:50 +08:00
feng
3fb0197e99 perf: 创建资产 nodes 可为空 默认 default 2023-04-21 14:57:50 +08:00
ibuler
7127b2da93 perf: 去掉 debug msg 2023-04-21 11:32:21 +08:00
ibuler
48b3699591 perf: 优化支持 自定义 applet
perf: 优化平台
2023-04-21 11:31:49 +08:00
1401 changed files with 46027 additions and 90197 deletions

View File

@@ -1,4 +1,5 @@
.git .git
logs/*
data/* data/*
.github .github
tmp/* tmp/*

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

@@ -0,0 +1,13 @@
---
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: 在这里添加关于问题的任何其他背景信息。

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

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

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

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

View File

@@ -12,9 +12,7 @@ jobs:
uses: actions-cool/issues-helper@v2 uses: actions-cool/issues-helper@v2
with: with:
actions: 'close-issues' actions: 'close-issues'
labels: '⏳ Pending feedback' labels: '状态:待反馈'
inactive-day: 30 inactive-day: 30
body: | 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。 您超过 30 天未反馈信息,我们将关闭该 issue如有需求您可以重新打开或者提交新的 issue。

View File

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

View File

@@ -13,53 +13,26 @@ jobs:
uses: actions-cool/issues-helper@v2 uses: actions-cool/issues-helper@v2
with: with:
actions: 'add-labels' actions: 'add-labels'
labels: '🔔 Pending processing' labels: '状态:待处理'
- name: Remove require reply label - name: Remove require reply label
uses: actions-cool/issues-helper@v2 uses: actions-cool/issues-helper@v2
with: with:
actions: 'remove-labels' actions: 'remove-labels'
labels: '⏳ Pending feedback' labels: '状态:待反馈'
add-label-if-is-member: add-label-if-not-author:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: (github.event.issue.user.id != github.event.comment.user.id) && !github.event.issue.pull_request && (github.event.issue.state == 'open')
steps: steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Get Organization name
id: org_name
run: echo "data=$(echo '${{ github.repository }}' | cut -d '/' -f 1)" >> $GITHUB_OUTPUT
- name: Get Organization public members
uses: octokit/request-action@v2.x
id: members
with:
route: GET /orgs/${{ steps.org_name.outputs.data }}/public_members
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Process public members data
# 将 members 中的数据转化为 login 字段的拼接字符串
id: member_names
run: echo "data=$(echo '${{ steps.members.outputs.data }}' | jq '[.[].login] | join(",")')" >> $GITHUB_OUTPUT
- run: "echo members: '${{ steps.members.outputs.data }}'"
- run: "echo member names: '${{ steps.member_names.outputs.data }}'"
- run: "echo comment user: '${{ github.event.comment.user.login }}'"
- run: "echo contains? : '${{ contains(steps.member_names.outputs.data, github.event.comment.user.login) }}'"
- name: Add require replay label - name: Add require replay label
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2 uses: actions-cool/issues-helper@v2
with: with:
actions: 'add-labels' actions: 'add-labels'
labels: '⏳ Pending feedback' labels: '状态:待反馈'
- name: Remove require handle label - name: Remove require handle label
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2 uses: actions-cool/issues-helper@v2
with: with:
actions: 'remove-labels' actions: 'remove-labels'
labels: '🔔 Pending processing' labels: '状态:待处理'

View File

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

View File

@@ -1,63 +1,36 @@
name: "Run Build Test" name: "Run Build Test"
on: on:
push: push:
paths: branches:
- 'Dockerfile' - pr@*
- 'Dockerfile*' - repr@*
- 'Dockerfile-*'
- 'pyproject.toml'
- 'poetry.lock'
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
component: [core]
version: [v4]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - uses: docker/setup-qemu-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare Build - uses: docker/setup-buildx-action@v2
run: |
sed -i 's@^FROM registry.fit2cloud.com/jumpserver@FROM ghcr.io/jumpserver@g' Dockerfile-ee
- name: Build CE Image - uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
push: true
file: Dockerfile
tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }}-ce
platforms: linux/amd64
build-args: |
VERSION=${{ matrix.version }}
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple
outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build EE Image
uses: docker/build-push-action@v5
with: with:
context: . context: .
push: false push: false
file: Dockerfile-ee tags: jumpserver/core:test
tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }} file: Dockerfile
platforms: linux/amd64
build-args: | build-args: |
VERSION=${{ matrix.version }}
APT_MIRROR=http://deb.debian.org APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple PIP_MIRROR=https://pypi.org/simple
outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true PIP_JMS_MIRROR=https://pypi.org/simple
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- uses: LouisBrunner/checks-action@v1.5.0
if: always()
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: Check Build
conclusion: ${{ job.status }}

View File

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

3
.gitignore vendored
View File

@@ -35,6 +35,7 @@ celerybeat-schedule.db
docs/_build/ docs/_build/
xpack xpack
xpack.bak xpack.bak
logs/*
### Vagrant ### ### Vagrant ###
.vagrant/ .vagrant/
release/* release/*
@@ -43,5 +44,3 @@ releashe
data/* data/*
test.py test.py
.history/ .history/
.test/
*.mo

View File

@@ -1,10 +1,5 @@
# Contributing # Contributing
As a contributor, you should agree that:
- The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
- Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
## Create pull request ## Create pull request
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it. PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.

View File

@@ -1,44 +1,16 @@
FROM debian:bullseye-slim AS stage-1 FROM python:3.9-slim as stage-build
ARG TARGETARCH ARG TARGETARCH
ARG DEPENDENCIES=" \
ca-certificates \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt
ARG CHECK_VERSION=v1.0.2
RUN set -ex \
&& wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& mv check /usr/local/bin/ \
&& chown root:root /usr/local/bin/check \
&& chmod 755 /usr/local/bin/check \
&& rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz
ARG VERSION ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver WORKDIR /opt/jumpserver
ADD . . ADD . .
RUN echo > /opt/jumpserver/config.yml \ RUN cd utils && bash -ixeu build.sh
&& \
if [ -n "${VERSION}" ]; then \
sed -i "s@VERSION = .*@VERSION = '${VERSION}'@g" apps/jumpserver/const.py; \
fi
FROM python:3.11-slim-bullseye AS stage-2 FROM python:3.9-slim
ARG TARGETARCH ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \ ARG BUILD_DEPENDENCIES=" \
g++ \ g++ \
@@ -46,94 +18,87 @@ ARG BUILD_DEPENDENCIES=" \
pkg-config" pkg-config"
ARG DEPENDENCIES=" \ ARG DEPENDENCIES=" \
default-libmysqlclient-dev \
freetds-dev \ freetds-dev \
gettext \ libpq-dev \
libkrb5-dev \ libffi-dev \
libjpeg-dev \
libldap2-dev \ libldap2-dev \
libsasl2-dev" libsasl2-dev \
libxml2-dev \
ARG APT_MIRROR=http://mirrors.ustc.edu.cn libxmlsec1-dev \
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ libxmlsec1-openssl \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \ libaio-dev"
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& 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,sharing=locked,id=core \
--mount=type=bind,source=poetry.lock,target=poetry.lock \
--mount=type=bind,source=pyproject.toml,target=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 --only main
COPY --from=stage-1 /opt/jumpserver /opt/jumpserver
RUN set -ex \
&& export SECRET_KEY=$(head -c100 < /dev/urandom | base64 | tr -dc A-Za-z0-9 | head -c 48) \
&& . /opt/py3/bin/activate \
&& cd apps \
&& python manage.py compilemessages
FROM python:3.11-slim-bullseye
ARG TARGETARCH
ENV LANG=en_US.UTF-8 \
PATH=/opt/py3/bin:$PATH
ARG DEPENDENCIES=" \
libldap2-dev \
libx11-dev"
ARG TOOLS=" \ ARG TOOLS=" \
ca-certificates \ ca-certificates \
curl \
default-libmysqlclient-dev \ default-libmysqlclient-dev \
default-mysql-client \
locales \
openssh-client \ openssh-client \
procps \
sshpass \ sshpass \
bubblewrap" telnet \
unzip \
vim \
git \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \ sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \ && rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \ && 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 ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \ && apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \ && 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 "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 "set mouse-=a" > ~/.vimrc \
&& echo "no" | dpkg-reconfigure dash \ && echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \ && sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc && sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/*
COPY --from=stage-2 /opt /opt ARG DOWNLOAD_URL=https://download.jumpserver.org
COPY --from=stage-1 /usr/local/bin /usr/local/bin
COPY --from=stage-1 /opt/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/ RUN mkdir -p /opt/oracle/ \
&& cd /opt/oracle/ \
&& wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
&& unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
&& sh -c "echo /opt/oracle/instantclient_19_10 > /etc/ld.so.conf.d/oracle-instantclient.conf" \
&& ldconfig \
&& rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
WORKDIR /opt/jumpserver WORKDIR /opt/jumpserver
ARG VERSION
ENV VERSION=$VERSION
VOLUME /opt/jumpserver/data VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENTRYPOINT ["./entrypoint.sh"] ENV LANG=zh_CN.UTF-8
EXPOSE 8080 EXPOSE 8080
STOPSIGNAL SIGQUIT ENTRYPOINT ["./entrypoint.sh"]
CMD ["start", "all"]

View File

@@ -1,52 +1,10 @@
ARG VERSION ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} AS build-xpack FROM jumpserver/core:${VERSION}
FROM python:3.11-slim-bullseye AS build-core COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
ARG BUILD_DEPENDENCIES=" \
g++"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple RUN --mount=type=cache,target=/root/.cache/pip \
RUN --mount=type=cache,target=/root/.cache,sharing=locked,id=core \
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
set -ex \ set -ex \
&& python3 -m venv /opt/py3 \ && pip install -r requirements/requirements_xpack.txt
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
&& poetry install --only xpack
FROM registry.fit2cloud.com/jumpserver/core:${VERSION}-ce
ARG TARGETARCH
ARG TOOLS=" \
curl \
iputils-ping \
netcat-openbsd \
nmap \
telnet \
vim \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${TOOLS}
COPY --from=build-core /opt/py3 /opt/py3
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack

96
Dockerfile.loong64 Normal file
View File

@@ -0,0 +1,96 @@
FROM python:3.9-slim as stage-build
ARG TARGETARCH
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
freetds-dev \
libpq-dev \
libffi-dev \
libjpeg-dev \
libldap2-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libaio-dev"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
locales \
openssh-client \
procps \
sshpass \
telnet \
unzip \
vim \
git \
wget"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
set -ex \
&& 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} \
&& 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 "set mouse-=a" > ~/.vimrc \
&& 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 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.0-cp39-cp39-linux_loongarch64.whl \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
WORKDIR /opt/jumpserver
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

210
README.md
View File

@@ -1,112 +1,126 @@
<div align="center"> <p align="center">
<a name="readme-top"></a> <a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
<a href="https://jumpserver.org/index-en.html"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a> </p>
<h3 align="center">广受欢迎的开源堡垒机</h3>
## An open-source PAM tool (Bastion Host) <p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
[![][license-shield]][license-link] <a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Docker pulls"></a>
[![][discord-shield]][discord-link] <a href="https://github.com/jumpserver/jumpserver/releases/latest"><img src="https://img.shields.io/github/v/release/jumpserver/jumpserver" alt="Latest release"></a>
[![][docker-shield]][docker-link] <a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
[![][github-release-shield]][github-release-link] </p>
[![][github-stars-shield]][github-stars-link]
**English** · [简体中文](./README.zh-CN.md) · [Documents][docs-link] · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]
</div>
<br/>
## What is JumpServer?
JumpServer is an open-source Privileged Access Management (PAM) tool that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and Remote App endpoints through a web browser.
![JumpServer Overview](https://github.com/jumpserver/jumpserver/assets/41712985/eb9e6f39-3911-4d4a-bca9-aa50cc3b761d)
## Screenshots
<table style="border-collapse: collapse; border: 1px solid black;">
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/99fabe5b-0475-4a53-9116-4c370a1426c4" alt="JumpServer Console" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/a424d731-1c70-4108-a7d8-5bbf387dda9a" alt="JumpServer Audits" /></td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/393d2c27-a2d0-4dea-882d-00ed509e00c9" alt="JumpServer Workbench" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/3a2611cd-8902-49b8-b82b-2a6dac851f3e" alt="JumpServer Settings" /></td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/1e236093-31f7-4563-8eb1-e36d865f1568" alt="JumpServer SSH" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/69373a82-f7ab-41e8-b763-bbad2ba52167" alt="JumpServer RDP" /></td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/5bed98c6-cbe8-4073-9597-d53c69dc3957" alt="JumpServer K8s" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/b80ad654-548f-42bc-ba3d-c1cfdf1b46d6" alt="JumpServer DB" /></td>
</tr>
</table>
## Quick Start
Prepare a clean Linux Server ( 64bit, >= 4c8g )
```
curl -sSL https://github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
```
Access JumpServer in your browser at `http://your-ip/`, `admin`/`admin`
## Components
JumpServer consists of multiple key components, which collectively form the functional framework of JumpServer, providing users with comprehensive capabilities for operations management and security control.
| Project | Status | Description |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer Character Protocol Connector |
| [Lion](https://github.com/jumpserver/lion) | <a href="https://github.com/jumpserver/lion/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion.svg" /></a> | JumpServer Graphical Protocol Connector |
| [Chen](https://github.com/jumpserver/chen) | <a href="https://github.com/jumpserver/chen/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen.svg" /> | JumpServer Web DB |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer RDP Proxy Connector |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Remote Application Connector (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Remote Application Connector (Linux) |
| [Magnus](https://github.com/jumpserver/magnus) | <img alt="Magnus" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Database Proxy Connector |
<p align="center">
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
<br>
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
## Contributing | :warning: 注意 :warning: |
|:-------------------------------------------------------------------------------------------------------------------------:|
| 3.0 架构上和 2.0 变化较大,建议全新安装一套环境来体验。如需升级,请务必升级前进行备份,并[查阅文档](https://kb.fit2cloud.com/?p=06638d69-f109-4333-b5bf-65b17b297ed9) |
Welcome to submit PR to contribute. Please refer to [CONTRIBUTING.md][contributing-link] for guidelines. --------------------------
## Security JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
JumpServer is a mission critical product. Please refer to the Basic Security Recommendations for installation and deployment. If you encounter any security-related issues, please contact us directly: ## 产品特色
- Email: support@fit2cloud.com - **开源**: 零门槛,线上快速获取和安装;
- **无插件**: 仅需浏览器,极致的 Web Terminal 使用体验;
- **分布式**: 支持分布式部署和横向扩展,轻松支持大规模并发访问;
- **多云支持**: 一套系统,同时管理不同云上面的资产;
- **多租户**: 一套系统,多个子公司或部门同时使用;
- **云端存储**: 审计录像云端存储,永不丢失;
- **多应用支持**: 全面支持各类资产包括服务器、数据库、Windows RemoteApp、Kubernetes 等;
- **安全可靠**: 被广泛使用、验证和信赖,连续 9 年的持续研发投入和产品更新升级。
## License ## UI 展示
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved. ![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png)
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 ## 在线体验
- 环境地址:<https://demo.jumpserver.org/>
| :warning: 注意 |
|:-----------------------------|
| 该环境仅作体验目的使用,我们会定时清理、重置数据! |
| 请勿修改体验环境用户的密码! |
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
## 快速开始
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [产品文档](https://docs.jumpserver.org)
- [在线学习](https://edu.fit2cloud.com/page/2635362)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
## 案例研究
- [腾讯海外游戏基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
- [万华化学通过JumpServer管理全球化分布式IT资产并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
- [雪花啤酒JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
- [顺丰科技JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
- [沐瞳游戏通过JumpServer管控多项目分布式资产](https://blog.fit2cloud.com/?p=3213)
- [携程JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
- [大智慧JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
- [小红书JumpServer 堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
- [中手游JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
- [中通快递JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
## 社区
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)
或加入到我们的社区当中进行进一步交流沟通。
### 微信交流群
<img src="https://download.jumpserver.org/images/wecom-group.jpeg" alt="微信群二维码" width="200"/>
### 参与贡献
欢迎提交 PR 参与贡献。感谢以下贡献者,他们让 JumpServer 变的越来越好。
<a href="https://github.com/jumpserver/jumpserver/graphs/contributors"><img src="https://opencollective.com/jumpserver/contributors.svg?width=890&button=false" /></a>
## 组件项目
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
## 安全说明
JumpServer是一款安全产品请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/)
进行安装部署。如果您发现安全相关问题,请直接联系我们:
- 邮箱support@fit2cloud.com
- 电话400-052-0755
## 致谢
- [Apache Guacamole](https://guacamole.apache.org/) Web 页面连接 RDP、SSH、VNC 等协议资产JumpServer Lion 组件使用到该项目;
- [OmniDB](https://omnidb.org/) Web 页面连接使用数据库JumpServer Web 数据库组件使用到该项目。
## License & Copyright
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, 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
https://www.gnu.org/licenses/gpl-3.0.html https://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an " AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
<!-- JumpServer official link --> language governing permissions and limitations under the License.
[docs-link]: https://en-docs.jumpserver.org/
[discord-link]: https://discord.com/invite/jcM5tKWJ
[contributing-link]: https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md
<!-- JumpServer Other link-->
[license-link]: https://www.gnu.org/licenses/gpl-3.0.html
[docker-link]: https://hub.docker.com/u/jumpserver
[github-release-link]: https://github.com/jumpserver/jumpserver/releases/latest
[github-stars-link]: https://github.com/jumpserver/jumpserver
[github-issues-link]: https://github.com/jumpserver/jumpserver/issues
<!-- Shield link-->
[github-release-shield]: https://img.shields.io/github/v/release/jumpserver/jumpserver
[github-stars-shield]: https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square
[docker-shield]: https://img.shields.io/docker/pulls/jumpserver/jms_all.svg
[license-shield]: https://img.shields.io/github/license/jumpserver/jumpserver
[discord-shield]: https://img.shields.io/discord/1194233267294052363?style=flat&logo=discord&logoColor=%23f5f5f5&labelColor=%235462eb&color=%235462eb
<!-- Image link -->

View File

@@ -1,125 +0,0 @@
<p align="center">
<a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
</p>
<h3 align="center">广受欢迎的开源堡垒机</h3>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
<a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Docker pulls"></a>
<a href="https://github.com/jumpserver/jumpserver/releases/latest"><img src="https://img.shields.io/github/v/release/jumpserver/jumpserver" alt="Latest release"></a>
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
<p align="center">
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
------------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
- **SSH**: Linux / Unix / 网络设备 等;
- **Windows**: Web 方式连接 / 原生 RDP 连接;
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
- **NoSQL**: Redis / MongoDB 等;
- **GPT**: ChatGPT 等;
- **云服务**: Kubernetes / VMware vSphere 等;
- **Web 站点**: 各类系统的 Web 管理后台;
- **应用**: 通过 Remote App 连接各类应用。
## 产品特色
- **开源**: 零门槛,线上快速获取和安装;
- **无插件**: 仅需浏览器,极致的 Web Terminal 使用体验;
- **分布式**: 支持分布式部署和横向扩展,轻松支持大规模并发访问;
- **多云支持**: 一套系统,同时管理不同云上面的资产;
- **多租户**: 一套系统,多个子公司或部门同时使用;
- **云端存储**: 审计录像云端存储,永不丢失;
## UI 展示
![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png)
## 在线体验
- 环境地址:<https://demo.jumpserver.org/>
| :warning: 注意 |
|:-----------------------------|
| 该环境仅作体验目的使用,我们会定时清理、重置数据! |
| 请勿修改体验环境用户的密码! |
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
## 快速开始
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [产品文档](https://docs.jumpserver.org)
- [在线学习](https://edu.fit2cloud.com/page/2635362)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
## 案例研究
- [腾讯音乐娱乐集团基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
- [腾讯海外游戏基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
- [万华化学通过JumpServer管理全球化分布式IT资产并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
- [雪花啤酒JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
- [顺丰科技JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
- [沐瞳游戏通过JumpServer管控多项目分布式资产](https://blog.fit2cloud.com/?p=3213)
- [携程JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
- [大智慧JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
- [小红书JumpServer 堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
- [中手游JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
- [中通快递JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
## 社区交流
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
### 参与贡献
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
## 组件项目
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core API 通信的组件项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
## 安全说明
JumpServer是一款安全产品请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/)
进行安装部署。如果您发现安全相关问题,请直接联系我们:
- 邮箱support@fit2cloud.com
- 电话400-052-0755
## License & Copyright
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, 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
https://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.

94
README_EN.md Normal file
View File

@@ -0,0 +1,94 @@
<p align="center"><a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a></p>
<h3 align="center">Open Source Bastion Host</h3>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
<a href="https://shields.io/github/downloads/jumpserver/jumpserver/total"><img src="https://shields.io/github/downloads/jumpserver/jumpserver/total" alt=" release"></a>
<a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Codacy"></a>
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
JumpServer is the world's first open-source Bastion Host and is licensed under the GPLv3. It is a 4A-compliant professional operation and maintenance security audit system.
JumpServer uses Python / Django for development, follows Web 2.0 specifications, and is equipped with an industry-leading Web Terminal solution that provides a beautiful user interface and great user experience
JumpServer adopts a distributed architecture to support multi-branch deployment across multiple cross-regional areas. The central node provides APIs, and login nodes are deployed in each branch. It can be scaled horizontally without concurrency restrictions.
Change the world by taking every little step
----
### Advantages
- Open Source: huge transparency and free to access with quick installation process.
- Distributed: support large-scale concurrent access with ease.
- No Plugin required: all you need is a browser, the ultimate Web Terminal experience.
- Multi-Cloud supported: a unified system to manage assets on different clouds at the same time
- Cloud storage: audit records are stored in the cloud. Data lost no more!
- Multi-Tenant system: multiple subsidiary companies or departments access the same system simultaneously.
- Many applications supported: link to databases, windows remote applications, and Kubernetes cluster, etc.
### JumpServer Component Projects
- [Lina](https://github.com/jumpserver/lina) JumpServer Web UI
- [Luna](https://github.com/jumpserver/luna) JumpServer Web Terminal
- [KoKo](https://github.com/jumpserver/koko) JumpServer Character protocaol Connector, replace original Python Version [Coco](https://github.com/jumpserver/coco)
- [Lion](https://github.com/jumpserver/lion-release) JumpServer Graphics protocol Connectorrely on [Apache Guacamole](https://guacamole.apache.org/)
### Contribution
If you have any good ideas or helping us to fix bugs, please submit a Pull Request and accept our thanks :)
Thanks to the following contributors for making JumpServer better everyday!
<a href="https://github.com/jumpserver/jumpserver/graphs/contributors">
<img src="https://contrib.rocks/image?repo=jumpserver/jumpserver" />
</a>
<a href="https://github.com/jumpserver/koko/graphs/contributors">
<img src="https://contrib.rocks/image?repo=jumpserver/koko" />
</a>
<a href="https://github.com/jumpserver/lina/graphs/contributors">
<img src="https://contrib.rocks/image?repo=jumpserver/lina" />
</a>
<a href="https://github.com/jumpserver/luna/graphs/contributors">
<img src="https://contrib.rocks/image?repo=jumpserver/luna" />
</a>
### Thanks to
- [Apache Guacamole](https://guacamole.apache.org/) Web page connection RDP, SSH, VNC protocol equipment. JumpServer graphical connection dependent.
- [OmniDB](https://omnidb.org/) Web page connection to databases. JumpServer Web database dependent.
### JumpServer Enterprise Version
- [Apply for it](https://jinshuju.net/f/kyOYpi)
### Case Study
- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
- [中通快递JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。
### For safety instructions
JumpServer is a security product. Please refer to [Basic Security Recommendations](https://docs.jumpserver.org/zh/master/install/install_security/) for deployment and installation.
If you find a security problem, please contact us directly
- ibuler@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
### License & Copyright
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
https://www.gnu.org/licenses/gpl-3.0.htmll
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View File

@@ -1,4 +1,3 @@
from .account import * from .account import *
from .task import * from .task import *
from .template import * from .template import *
from .virtual import *

View File

@@ -6,12 +6,11 @@ from rest_framework.status import HTTP_200_OK
from accounts import serializers from accounts import serializers
from accounts.filters import AccountFilterSet from accounts.filters import AccountFilterSet
from accounts.mixins import AccountRecordViewLogMixin
from accounts.models import Account from accounts.models import Account
from assets.models import Asset, Node from assets.models import Asset, Node
from authentication.permissions import UserConfirmation, ConfirmType from common.api import ExtraFilterFieldsMixin
from common.api.mixin import ExtraFilterFieldsMixin from common.permissions import UserConfirmation, ConfirmType, IsValidUser
from common.permissions import IsValidUser from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
@@ -23,18 +22,16 @@ __all__ = [
class AccountViewSet(OrgBulkModelViewSet): class AccountViewSet(OrgBulkModelViewSet):
model = Account model = Account
search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment') search_fields = ('username', 'asset__address', 'name')
filterset_class = AccountFilterSet filterset_class = AccountFilterSet
serializer_classes = { serializer_classes = {
'default': serializers.AccountSerializer, 'default': serializers.AccountSerializer,
'retrieve': serializers.AccountDetailSerializer,
} }
rbac_perms = { rbac_perms = {
'partial_update': ['accounts.change_account'], 'partial_update': ['accounts.change_account'],
'su_from_accounts': 'accounts.view_account', 'su_from_accounts': 'accounts.view_account',
'clear_secret': 'accounts.change_account', 'clear_secret': 'accounts.change_account',
} }
export_as_zip = True
@action(methods=['get'], detail=False, url_path='su-from-accounts') @action(methods=['get'], detail=False, url_path='su-from-accounts')
def su_from_accounts(self, request, *args, **kwargs): def su_from_accounts(self, request, *args, **kwargs):
@@ -54,23 +51,22 @@ class AccountViewSet(OrgBulkModelViewSet):
return Response(data=serializer.data) return Response(data=serializer.data)
@action( @action(
methods=['post'], detail=False, url_path='username-suggestions', methods=['get'], detail=False, url_path='username-suggestions',
permission_classes=[IsValidUser] permission_classes=[IsValidUser]
) )
def username_suggestions(self, request, *args, **kwargs): def username_suggestions(self, request, *args, **kwargs):
asset_ids = request.data.get('assets', []) asset_ids = request.query_params.get('assets')
node_ids = request.data.get('nodes', []) node_keys = request.query_params.get('keys')
username = request.data.get('username', '') username = request.query_params.get('username')
accounts = Account.objects.all()
if node_ids:
nodes = Node.objects.filter(id__in=node_ids)
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
asset_ids.extend(node_asset_ids)
assets = Asset.objects.all()
if asset_ids: if asset_ids:
accounts = accounts.filter(asset_id__in=list(set(asset_ids))) assets = assets.filter(id__in=asset_ids.split(','))
if node_keys:
patten = Node.get_node_all_children_key_pattern(node_keys.split(','))
assets = assets.filter(nodes__key__regex=patten)
accounts = Account.objects.filter(asset__in=assets)
if username: if username:
accounts = accounts.filter(username__icontains=username) accounts = accounts.filter(username__icontains=username)
usernames = list(accounts.values_list('username', flat=True).distinct()[:10]) usernames = list(accounts.values_list('username', flat=True).distinct()[:10])
@@ -87,7 +83,7 @@ class AccountViewSet(OrgBulkModelViewSet):
return Response(status=HTTP_200_OK) return Response(status=HTTP_200_OK)
class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet): class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
""" """
因为可能要导出所有账号,所以单独建立了一个 viewset 因为可能要导出所有账号,所以单独建立了一个 viewset
""" """
@@ -116,7 +112,7 @@ class AssetAccountBulkCreateApi(CreateAPIView):
return Response(data=serializer.data, status=HTTP_200_OK) return Response(data=serializer.data, status=HTTP_200_OK)
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, ListAPIView): class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, ListAPIView):
model = Account.history.model model = Account.history.model
serializer_class = serializers.AccountHistorySerializer serializer_class = serializers.AccountHistorySerializer
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']
@@ -135,12 +131,11 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixi
def get_queryset(self): def get_queryset(self):
account = self.get_object() account = self.get_object()
histories = account.history.all() histories = account.history.all()
latest_history = account.history.first() last_history = account.history.first()
if not latest_history: if not last_history:
return histories return histories
if account.secret != latest_history.secret:
return histories if account.secret == last_history.secret \
if account.secret_type != latest_history.secret_type: and account.secret_type == last_history.secret_type:
return histories histories = histories.exclude(history_id=last_history.history_id)
histories = histories.exclude(history_id=latest_history.history_id)
return histories return histories

View File

@@ -1,13 +1,9 @@
from django.db.models import Q
from rest_framework.generics import CreateAPIView from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from accounts import serializers from accounts import serializers
from accounts.models import Account from accounts.tasks import verify_accounts_connectivity_task, push_accounts_to_assets_task
from accounts.permissions import AccountTaskActionPermission from assets.exceptions import NotSupportedTemporarilyError
from accounts.tasks import (
remove_accounts_task, verify_accounts_connectivity_task, push_accounts_to_assets_task
)
from authentication.permissions import UserConfirmation, ConfirmType
__all__ = [ __all__ = [
'AccountsTaskCreateAPI', 'AccountsTaskCreateAPI',
@@ -16,48 +12,38 @@ __all__ = [
class AccountsTaskCreateAPI(CreateAPIView): class AccountsTaskCreateAPI(CreateAPIView):
serializer_class = serializers.AccountTaskSerializer serializer_class = serializers.AccountTaskSerializer
permission_classes = (AccountTaskActionPermission,)
def get_permissions(self): def check_permissions(self, request):
act = self.request.data.get('action') act = request.data.get('action')
if act == 'remove': if act == 'push':
self.permission_classes = [ code = 'accounts.push_account'
AccountTaskActionPermission, else:
UserConfirmation.require(ConfirmType.PASSWORD) code = 'accounts.verify_account'
] return request.user.has_perm(code)
return super().get_permissions()
@staticmethod
def get_account_ids(data, action):
account_type = 'gather_accounts' if action == 'remove' else 'accounts'
accounts = data.get(account_type, [])
account_ids = [str(a.id) for a in accounts]
if action == 'remove':
return account_ids
assets = data.get('assets', [])
asset_ids = [str(a.id) for a in assets]
ids = Account.objects.filter(
Q(id__in=account_ids) | Q(asset_id__in=asset_ids)
).distinct().values_list('id', flat=True)
return [str(_id) for _id in ids]
def perform_create(self, serializer): def perform_create(self, serializer):
data = serializer.validated_data data = serializer.validated_data
action = data['action'] accounts = data.get('accounts', [])
ids = self.get_account_ids(data, action) params = data.get('params')
account_ids = [str(a.id) for a in accounts]
if action == 'push': if data['action'] == 'push':
task = push_accounts_to_assets_task.delay(ids, data.get('params')) task = push_accounts_to_assets_task.delay(account_ids, params)
elif action == 'remove':
task = remove_accounts_task.delay(ids)
elif action == 'verify':
task = verify_accounts_connectivity_task.delay(ids)
else: else:
raise ValueError(f"Invalid action: {action}") account = accounts[0]
asset = account.asset
if not asset.auto_config['ansible_enabled'] or \
not asset.auto_config['ping_enabled']:
raise NotSupportedTemporarilyError()
task = verify_accounts_connectivity_task.delay(account_ids)
data = getattr(serializer, '_data', {}) data = getattr(serializer, '_data', {})
data["task"] = task.id data["task"] = task.id
setattr(serializer, '_data', data) setattr(serializer, '_data', data)
return task return task
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return handler

View File

@@ -1,15 +1,11 @@
from django_filters import rest_framework as drf_filters from django_filters import rest_framework as drf_filters
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers from accounts import serializers
from accounts.mixins import AccountRecordViewLogMixin
from accounts.models import AccountTemplate from accounts.models import AccountTemplate
from accounts.tasks import template_sync_related_accounts
from assets.const import Protocol from assets.const import Protocol
from authentication.permissions import UserConfirmation, ConfirmType
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
@@ -42,30 +38,11 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
filterset_class = AccountTemplateFilterSet filterset_class = AccountTemplateFilterSet
search_fields = ('username', 'name') search_fields = ('username', 'name')
serializer_classes = { serializer_classes = {
'default': serializers.AccountTemplateSerializer, 'default': serializers.AccountTemplateSerializer
}
rbac_perms = {
'su_from_account_templates': 'accounts.view_accounttemplate',
'sync_related_accounts': 'accounts.change_account',
} }
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
def su_from_account_templates(self, request, *args, **kwargs):
pk = request.query_params.get('template_id')
templates = AccountTemplate.get_su_from_account_templates(pk)
templates = self.filter_queryset(templates)
serializer = self.get_serializer(templates, many=True)
return Response(data=serializer.data)
@action(methods=['patch'], detail=True, url_path='sync-related-accounts') class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
def sync_related_accounts(self, request, *args, **kwargs):
instance = self.get_object()
user_id = str(request.user.id)
task = template_sync_related_accounts.delay(str(instance.id), user_id)
return Response({'task': task.id}, status=status.HTTP_200_OK)
class AccountTemplateSecretsViewSet(AccountRecordViewLogMixin, AccountTemplateViewSet):
serializer_classes = { serializer_classes = {
'default': serializers.AccountTemplateSecretSerializer, 'default': serializers.AccountTemplateSecretSerializer,
} }

View File

@@ -1,20 +0,0 @@
from django.shortcuts import get_object_or_404
from accounts.models import VirtualAccount
from accounts.serializers import VirtualAccountSerializer
from common.utils import is_uuid
from orgs.mixins.api import OrgBulkModelViewSet
class VirtualAccountViewSet(OrgBulkModelViewSet):
serializer_class = VirtualAccountSerializer
search_fields = ('alias',)
filterset_fields = ('alias',)
def get_queryset(self):
return VirtualAccount.get_or_init_queryset()
def get_object(self, ):
pk = self.kwargs.get('pk')
kwargs = {'pk': pk} if is_uuid(pk) else {'alias': pk}
return get_object_or_404(VirtualAccount, **kwargs)

View File

@@ -18,15 +18,16 @@ __all__ = [
class AccountBackupPlanViewSet(OrgBulkModelViewSet): class AccountBackupPlanViewSet(OrgBulkModelViewSet):
model = AccountBackupAutomation model = AccountBackupAutomation
filterset_fields = ('name',) filter_fields = ('name',)
search_fields = filterset_fields search_fields = filter_fields
ordering = ('name',)
serializer_class = serializers.AccountBackupSerializer serializer_class = serializers.AccountBackupSerializer
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet): class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AccountBackupPlanExecutionSerializer serializer_class = serializers.AccountBackupPlanExecutionSerializer
search_fields = ('trigger', 'plan__name') search_fields = ('trigger',)
filterset_fields = ('trigger', 'plan_id', 'plan__name') filterset_fields = ('trigger', 'plan_id')
http_method_names = ['get', 'post', 'options'] http_method_names = ['get', 'post', 'options']
def get_queryset(self): def get_queryset(self):

View File

@@ -1,5 +1,5 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import status, mixins, viewsets from rest_framework import status, mixins, viewsets
from rest_framework.response import Response from rest_framework.response import Response
@@ -20,8 +20,8 @@ __all__ = [
class AutomationAssetsListApi(generics.ListAPIView): class AutomationAssetsListApi(generics.ListAPIView):
model = BaseAutomation model = BaseAutomation
serializer_class = serializers.AutomationAssetsSerializer serializer_class = serializers.AutomationAssetsSerializer
filterset_fields = ("name", "address") filter_fields = ("name", "address")
search_fields = filterset_fields search_fields = filter_fields
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
@@ -95,8 +95,8 @@ class AutomationExecutionViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet mixins.RetrieveModelMixin, viewsets.GenericViewSet
): ):
search_fields = ('trigger', 'automation__name') search_fields = ('trigger',)
filterset_fields = ('trigger', 'automation_id', 'automation__name') filterset_fields = ('trigger', 'automation_id')
serializer_class = serializers.AutomationExecutionSerializer serializer_class = serializers.AutomationExecutionSerializer
tp: str tp: str

View File

@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import status, mixins
from rest_framework.decorators import action from rest_framework import mixins
from rest_framework.response import Response
from accounts import serializers from accounts import serializers
from accounts.const import AutomationTypes from accounts.const import AutomationTypes
from accounts.filters import ChangeSecretRecordFilterSet from accounts.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord from common.utils import get_object_or_none
from accounts.tasks import execute_automation_record_task
from authentication.permissions import UserConfirmation, ConfirmType
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from rbac.permissions import RBACPermission
from .base import ( from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi, AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
AutomationNodeAddRemoveApi, AutomationExecutionViewSet AutomationNodeAddRemoveApi, AutomationExecutionViewSet
@@ -27,53 +23,28 @@ __all__ = [
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet): class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
model = ChangeSecretAutomation model = ChangeSecretAutomation
filterset_fields = ('name', 'secret_type', 'secret_strategy') filter_fields = ('name', 'secret_type', 'secret_strategy')
search_fields = filterset_fields search_fields = filter_fields
serializer_class = serializers.ChangeSecretAutomationSerializer serializer_class = serializers.ChangeSecretAutomationSerializer
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
filterset_class = ChangeSecretRecordFilterSet serializer_class = serializers.ChangeSecretRecordSerializer
search_fields = ('asset__address',) filter_fields = ['asset', 'execution_id']
tp = AutomationTypes.change_secret search_fields = ['asset__hostname']
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): def get_queryset(self):
return ChangeSecretRecord.objects.all() return ChangeSecretRecord.objects.filter(
execution__automation__type=AutomationTypes.change_secret
@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:
return Response(
{'detail': 'Only one execution is allowed to execute'},
status=status.HTTP_400_BAD_REQUEST
) )
task = execute_automation_record_task.delay(record_ids, self.tp)
return Response({'task': task.id}, status=status.HTTP_200_OK)
@action(methods=['get'], detail=True, url_path='secret') def filter_queryset(self, queryset):
def secret(self, request, *args, **kwargs): queryset = super().filter_queryset(queryset)
instance = self.get_object() eid = self.request.query_params.get('execution_id')
serializer = self.get_serializer(instance) execution = get_object_or_none(AutomationExecution, pk=eid)
return Response(serializer.data) if execution:
queryset = queryset.filter(execution=execution)
return queryset
class ChangSecretExecutionViewSet(AutomationExecutionViewSet): class ChangSecretExecutionViewSet(AutomationExecutionViewSet):

View File

@@ -20,8 +20,8 @@ __all__ = [
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet): class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
model = GatherAccountsAutomation model = GatherAccountsAutomation
filterset_fields = ('name',) filter_fields = ('name',)
search_fields = filterset_fields search_fields = filter_fields
serializer_class = serializers.GatherAccountAutomationSerializer serializer_class = serializers.GatherAccountAutomationSerializer

View File

@@ -20,8 +20,8 @@ __all__ = [
class PushAccountAutomationViewSet(OrgBulkModelViewSet): class PushAccountAutomationViewSet(OrgBulkModelViewSet):
model = PushAccountAutomation model = PushAccountAutomation
filterset_fields = ('name', 'secret_type', 'secret_strategy') filter_fields = ('name', 'secret_type', 'secret_strategy')
search_fields = filterset_fields search_fields = filter_fields
serializer_class = serializers.PushAccountAutomationSerializer serializer_class = serializers.PushAccountAutomationSerializer
@@ -42,7 +42,6 @@ class PushAccountExecutionViewSet(AutomationExecutionViewSet):
class PushAccountRecordViewSet(ChangeSecretRecordViewSet): class PushAccountRecordViewSet(ChangeSecretRecordViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer serializer_class = serializers.ChangeSecretRecordSerializer
tp = AutomationTypes.push_account
def get_queryset(self): def get_queryset(self):
return ChangeSecretRecord.objects.filter( return ChangeSecretRecord.objects.filter(

View File

@@ -4,8 +4,8 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig): class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts' name = 'accounts'
verbose_name = 'App Accounts'
def ready(self): def ready(self):
from . import signal_handlers # noqa from . import signal_handlers
from . import tasks # noqa from . import tasks
__all__ = signal_handlers

View File

@@ -1,27 +1,24 @@
import os import os
import time import time
from openpyxl import Workbook
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.db.models import F
from rest_framework import serializers from rest_framework import serializers
from xlsxwriter import Workbook
from accounts.const import AccountBackupType from accounts.models import Account
from accounts.models.automations.backup_account import AccountBackupAutomation
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
from accounts.serializers import AccountSecretSerializer
from assets.const import AllTypes from assets.const import AllTypes
from common.utils.file import encrypt_and_compress_zip_file, zip_files from accounts.serializers import AccountSecretSerializer
from common.utils.timezone import local_now_filename, local_now_display from accounts.notifications import AccountBackupExecutionTaskMsg
from terminal.models.component.storage import ReplayStorage
from users.models import User from users.models import User
from common.utils import get_logger
from common.utils.timezone import local_now_display
from common.utils.file import encrypt_and_compress_zip_file
logger = get_logger(__file__)
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
split_help_text = _('The account key will be split into two parts and sent')
class RecipientsNotFound(Exception):
pass
class BaseAccountHandler: class BaseAccountHandler:
@@ -75,26 +72,12 @@ class AssetAccountHandler(BaseAccountHandler):
@staticmethod @staticmethod
def get_filename(plan_name): def get_filename(plan_name):
filename = os.path.join( filename = os.path.join(
PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.xlsx' PATH, f'{plan_name}-{local_now_display()}-{time.time()}.xlsx'
) )
return filename return filename
@staticmethod
def handler_secret(data, section):
for account_data in data:
secret = account_data.get('secret')
if not secret:
continue
length = len(secret)
index = length // 2
if section == "front":
secret = secret[:index] + '*' * (length - index)
elif section == "back":
secret = '*' * (length - index) + secret[index:]
account_data['secret'] = secret
@classmethod @classmethod
def create_data_map(cls, accounts, section): def create_data_map(cls, accounts):
data_map = defaultdict(list) data_map = defaultdict(list)
if not accounts.exists(): if not accounts.exists():
@@ -114,10 +97,9 @@ class AssetAccountHandler(BaseAccountHandler):
for tp, _accounts in account_type_map.items(): for tp, _accounts in account_type_map.items():
sheet_name = type_dict.get(tp, tp) sheet_name = type_dict.get(tp, tp)
data = AccountSecretSerializer(_accounts, many=True).data data = AccountSecretSerializer(_accounts, many=True).data
cls.handler_secret(data, section)
data_map.update(cls.add_rows(data, header_fields, sheet_name)) data_map.update(cls.add_rows(data, header_fields, sheet_name))
number_of_backup_accounts = _('Number of backup accounts')
print('\n\033[33m- {}: {}\033[0m'.format(number_of_backup_accounts, accounts.count())) logger.info('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
return data_map return data_map
@@ -127,18 +109,17 @@ class AccountBackupHandler:
self.plan_name = self.execution.plan.name self.plan_name = self.execution.plan.name
self.is_frozen = False # 任务状态冻结标志 self.is_frozen = False # 任务状态冻结标志
def create_excel(self, section='complete'): def create_excel(self):
hint = _('Generating asset or application related backup information files') logger.info(
print(
'\n' '\n'
f'\033[32m>>> {hint}\033[0m' '\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
'' ''
) )
# Print task start date # Print task start date
time_start = time.time() time_start = time.time()
files = [] files = []
accounts = self.execution.backup_accounts accounts = self.execution.backup_accounts
data_map = AssetAccountHandler.create_data_map(accounts, section) data_map = AssetAccountHandler.create_data_map(accounts)
if not data_map: if not data_map:
return files return files
@@ -146,25 +127,22 @@ class AccountBackupHandler:
wb = Workbook(filename) wb = Workbook(filename)
for sheet, data in data_map.items(): for sheet, data in data_map.items():
ws = wb.add_worksheet(str(sheet)) ws = wb.create_sheet(str(sheet))
for row_index, row_data in enumerate(data): for row in data:
for col_index, col_data in enumerate(row_data): ws.append(row)
ws.write_string(row_index, col_index, col_data) wb.save(filename)
wb.close()
files.append(filename) files.append(filename)
timedelta = round((time.time() - time_start), 2) timedelta = round((time.time() - time_start), 2)
time_cost = _('Time cost') logger.info('步骤完成: 用时 {}s'.format(timedelta))
file_created = _('Backup file creation completed')
print('{}: {} {}s'.format(file_created, time_cost, timedelta))
return files return files
def send_backup_mail(self, files, recipients): def send_backup_mail(self, files, recipients):
if not files: if not files:
return return
recipients = User.objects.filter(id__in=list(recipients)) recipients = User.objects.filter(id__in=list(recipients))
print( logger.info(
'\n' '\n'
f'\033[32m>>> {_("Start sending backup emails")}\033[0m' '\033[32m>>> 发送备份邮件\033[0m'
'' ''
) )
plan_name = self.plan_name plan_name = self.plan_name
@@ -172,37 +150,12 @@ class AccountBackupHandler:
if not user.secret_key: if not user.secret_key:
attachment_list = [] attachment_list = []
else: else:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip') password = user.secret_key.encode('utf8')
encrypt_and_compress_zip_file(attachment, user.secret_key, files) attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, files)
attachment_list = [attachment, ] attachment_list = [attachment, ]
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list) AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
email_sent_to = _('Email sent to') logger.info('邮件已发送至{}({})'.format(user, user.email))
print('{} {}({})'.format(email_sent_to, user, user.email))
for file in files:
os.remove(file)
def send_backup_obj_storage(self, files, recipients, password):
if not files:
return
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
print(
'\n'
'\033[32m>>> 📃 ---> sftp \033[0m'
''
)
plan_name = self.plan_name
encrypt_file = _('Encrypting files using encryption password')
for rec in recipients:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
if password:
print(f'\033[32m>>> {encrypt_file}\033[0m')
encrypt_and_compress_zip_file(attachment, password, files)
else:
zip_files(attachment, files)
attachment_list = attachment
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
file_sent_to = _('The backup file will be sent to')
print('{}: {}({})'.format(file_sent_to, rec.name, rec.id))
for file in files: for file in files:
os.remove(file) os.remove(file)
@@ -210,28 +163,33 @@ class AccountBackupHandler:
self.execution.reason = reason[:1024] self.execution.reason = reason[:1024]
self.execution.is_success = is_success self.execution.is_success = is_success
self.execution.save() self.execution.save()
finish = _('Finish') logger.info('已完成对任务状态的更新')
print(f'\n{finish}\n')
@staticmethod def step_finished(self, is_success):
def step_finished(is_success):
if is_success: if is_success:
print(_('Success')) logger.info('任务执行成功')
else: else:
print(_('Failed')) logger.error('任务执行失败')
def _run(self): def _run(self):
is_success = False is_success = False
error = '-' error = '-'
try: try:
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email.value) recipients = self.execution.plan_snapshot.get('recipients')
if backup_type == AccountBackupType.email.value: if not recipients:
self.backup_by_email() logger.info(
elif backup_type == AccountBackupType.object_storage.value: '\n'
self.backup_by_obj_storage() '\033[32m>>> 该备份任务未分配收件人\033[0m'
''
)
else:
files = self.create_excel()
self.send_backup_mail(files, recipients)
except Exception as e: except Exception as e:
self.is_frozen = True self.is_frozen = True
print(e) logger.error('任务执行被异常中断')
logger.info('下面打印发生异常的 Traceback 信息 : ')
logger.error(e, exc_info=True)
error = str(e) error = str(e)
else: else:
is_success = True is_success = True
@@ -240,68 +198,16 @@ class AccountBackupHandler:
self.step_perform_task_update(is_success, reason) self.step_perform_task_update(is_success, reason)
self.step_finished(is_success) self.step_finished(is_success)
def backup_by_obj_storage(self):
object_id = self.execution.snapshot.get('id')
zip_encrypt_password = AccountBackupAutomation.objects.get(id=object_id).zip_encrypt_password
obj_recipients_part_one = self.execution.snapshot.get('obj_recipients_part_one', [])
obj_recipients_part_two = self.execution.snapshot.get('obj_recipients_part_two', [])
no_assigned_sftp_server = _('The backup task has no assigned sftp server')
if not obj_recipients_part_one and not obj_recipients_part_two:
print(
'\n'
f'\033[31m>>> {no_assigned_sftp_server}\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
if obj_recipients_part_one and obj_recipients_part_two:
print(f'\033[32m>>> {split_help_text}\033[0m')
files = self.create_excel(section='front')
self.send_backup_obj_storage(files, obj_recipients_part_one, zip_encrypt_password)
files = self.create_excel(section='back')
self.send_backup_obj_storage(files, obj_recipients_part_two, zip_encrypt_password)
else:
recipients = obj_recipients_part_one or obj_recipients_part_two
files = self.create_excel()
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
def backup_by_email(self):
warn_text = _('The backup task has no assigned recipient')
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
if not recipients_part_one and not recipients_part_two:
print(
'\n'
f'\033[31m>>> {warn_text}\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
if recipients_part_one and recipients_part_two:
print(f'\033[32m>>> {split_help_text}\033[0m')
files = self.create_excel(section='front')
self.send_backup_mail(files, recipients_part_one)
files = self.create_excel(section='back')
self.send_backup_mail(files, recipients_part_two)
else:
recipients = recipients_part_one or recipients_part_two
files = self.create_excel()
self.send_backup_mail(files, recipients)
def run(self): def run(self):
plan_start = _('Plan start') logger.info('任务开始: {}'.format(local_now_display()))
plan_end = _('Plan end')
time_cost = _('Time cost')
error = _('An exception occurred during task execution')
print('{}: {}'.format(plan_start, local_now_display()))
time_start = time.time() time_start = time.time()
try: try:
self._run() self._run()
except Exception as e: except Exception as e:
print(error) logger.error('任务运行出现异常')
print(e) logger.error('下面显示异常 Traceback 信息: ')
logger.error(e, exc_info=True)
finally: finally:
print('\n{}: {}'.format(plan_end, local_now_display())) logger.info('\n任务结束: {}'.format(local_now_display()))
timedelta = round((time.time() - time_start), 2) timedelta = round((time.time() - time_start), 2)
print('{}: {}s'.format(time_cost, timedelta)) logger.info('用时: {}'.format(timedelta))

View File

@@ -3,11 +3,14 @@
import time import time
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from common.utils.timezone import local_now_display from common.utils.timezone import local_now_display
from .handlers import AccountBackupHandler from .handlers import AccountBackupHandler
logger = get_logger(__name__)
class AccountBackupManager: class AccountBackupManager:
def __init__(self, execution): def __init__(self, execution):
@@ -20,8 +23,7 @@ class AccountBackupManager:
def do_run(self): def do_run(self):
execution = self.execution execution = self.execution
account_backup_execution_being_executed = _('The account backup plan is being executed') logger.info('\n\033[33m# 账号备份计划正在执行\033[0m')
print(f'\n\033[33m# {account_backup_execution_being_executed}\033[0m')
handler = AccountBackupHandler(execution) handler = AccountBackupHandler(execution)
handler.run() handler.run()
@@ -33,12 +35,10 @@ class AccountBackupManager:
self.time_end = time.time() self.time_end = time.time()
self.date_end = timezone.now() self.date_end = timezone.now()
print('\n\n' + '-' * 80) logger.info('\n\n' + '-' * 80)
plan_execution_end = _('Plan execution end') logger.info('计划执行结束 {}\n'.format(local_now_display()))
print('{} {}\n'.format(plan_execution_end, local_now_display()))
self.timedelta = self.time_end - self.time_start self.timedelta = self.time_end - self.time_start
time_cost = _('Time cost') logger.info('用时: {}s'.format(self.timedelta))
print('{}: {}s'.format(time_cost, self.timedelta))
self.execution.timedelta = self.timedelta self.execution.timedelta = self.timedelta
self.execution.save() self.execution.save()

View File

@@ -2,10 +2,9 @@
gather_facts: no gather_facts: no
vars: vars:
ansible_connection: local ansible_connection: local
ansible_become: false
tasks: tasks:
- name: Test privileged account (paramiko) - name: Test privileged account
ssh_ping: ssh_ping:
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
@@ -13,17 +12,9 @@
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}" login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}" 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) }}"
register: ping_info register: ping_info
delegate_to: localhost
- name: Change asset password (paramiko) - name: Change asset password
custom_command: custom_command:
login_user: "{{ jms_account.username }}" login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
@@ -31,31 +22,19 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}" login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}" 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) }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
commands: "{{ params.commands }}" commands: "{{ params.commands }}"
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}" first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
ignore_errors: true
when: ping_info is succeeded when: ping_info is succeeded
register: change_info register: change_info
delegate_to: localhost
- name: Verify password (paramiko) - name: Verify password
ssh_ping: ssh_ping:
login_user: "{{ account.username }}" login_user: "{{ account.username }}"
login_password: "{{ account.secret }}" login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
become: "{{ account.become.ansible_become | default(False) }}" when:
become_method: su - ping_info is succeeded
become_user: "{{ account.become.ansible_user | default('') }}" - change_info is succeeded
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

@@ -6,27 +6,14 @@ category:
type: type:
- all - all
method: change_secret method: change_secret
protocol: ssh
priority: 50
params: params:
- name: commands - name: commands
type: list type: list
label: "{{ 'Params commands label' | trans }}" label: '自定义命令'
default: [ '' ] default: [ '' ]
help_text: "{{ 'Params commands help text' | trans }}" help_text: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
i18n: i18n:
SSH account change secret: SSH account change secret:
zh: '使用 SSH 命令行自定义改密' zh: SSH 账号改密
ja: 'SSH コマンドライン方式でカスタムパスワード変更' ja: SSH アカウントのパスワード変更
en: 'Custom password change by SSH command line'
Params commands help text:
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />&#123;ユーザー名&#125;、&#123;パスワード&#125;、&#123;login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.&#123;login_password&#125;<br />3 .ターミナルの設定<br / >4. ユーザー名 &#123;ユーザー名&#125; 権限 0 パスワード &#123;パスワード&#125; <br />5. 終了'
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use &#123;username&#125;, &#123;password&#125;, &#123;login_password&# 125; format, which will be replaced when executing the task. <br />For example, to change the password of a Cisco host, you generally need to configure five commands:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br / >4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
Params commands label:
zh: '自定义命令'
ja: 'カスタムコマンド'
en: 'Custom command'

View File

@@ -1,7 +1,7 @@
- hosts: mongodb - hosts: mongodb
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test MongoDB connection - name: Test MongoDB connection
@@ -11,9 +11,9 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl | default('') }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info register: db_info
@@ -31,15 +31,15 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}" db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded when: db_info is succeeded
register: change_info
- name: Verify password - name: Verify password
mongodb_ping: mongodb_ping:
@@ -49,7 +49,10 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -7,6 +7,5 @@ method: change_secret
i18n: i18n:
MongoDB account change secret: MongoDB account change secret:
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号改密 zh: MongoDB 账号改密
ja: Ansible mongodb モジュールを使用して MongoDB アカウントのパスワード変更 ja: MongoDB アカウントのパスワード変更
en: Using Ansible module mongodb to change MongoDB account secret

View File

@@ -1,9 +1,8 @@
- hosts: mysql - hosts: mysql
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}" db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks: tasks:
- name: Test MySQL connection - name: Test MySQL connection
@@ -12,10 +11,6 @@
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version filter: version
register: db_info register: db_info
@@ -29,16 +24,12 @@
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
host: "%" host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}" priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded when: db_info is succeeded
register: change_info
- name: Verify password - name: Verify password
community.mysql.mysql_info: community.mysql.mysql_info:
@@ -46,8 +37,7 @@
login_password: "{{ account.secret }}" login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version filter: version
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -8,6 +8,5 @@ method: change_secret
i18n: i18n:
MySQL account change secret: MySQL account change secret:
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号改密 zh: MySQL 账号改密
ja: Ansible mysql モジュールを使用して MySQL アカウントのパスワード変更 ja: MySQL アカウントのパスワード変更
en: Using Ansible module mysql to change MySQL account secret

View File

@@ -1,7 +1,7 @@
- hosts: oracle - hosts: oracle
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test Oracle connection - name: Test Oracle connection
@@ -29,8 +29,8 @@
mode: "{{ jms_account.mode }}" mode: "{{ jms_account.mode }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded when: db_info is succeeded
register: change_info
- name: Verify password - name: Verify password
oracle_ping: oracle_ping:
@@ -39,4 +39,6 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}" when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -1,7 +1,7 @@
- hosts: postgre - hosts: postgre
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test PostgreSQL connection - name: Test PostgreSQL connection
@@ -29,8 +29,8 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
role_attr_flags: LOGIN role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded when: result is succeeded
register: change_info
- name: Verify password - name: Verify password
community.postgresql.postgresql_ping: community.postgresql.postgresql_ping:
@@ -39,3 +39,8 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}" 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

@@ -1,7 +1,7 @@
- hosts: sqlserver - hosts: sqlserver
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test SQLServer connection - name: Test SQLServer connection
@@ -40,9 +40,9 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version" script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0 when: user_exist.query_results[0] | length != 0
register: change_info
- name: Add SQLServer user - name: Add SQLServer user
community.general.mssql_script: community.general.mssql_script:
@@ -51,9 +51,9 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; CREATE USER {{ account.username }} FOR LOGIN {{ account.username }}; select @@version" script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0 when: user_exist.query_results[0] | length == 0
register: change_info
- name: Verify password - name: Verify password
community.general.mssql_script: community.general.mssql_script:
@@ -64,3 +64,6 @@
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: | script: |
SELECT @@version SELECT @@version
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -1,48 +1,21 @@
- hosts: demo - hosts: demo
gather_facts: no gather_facts: no
tasks: tasks:
- name: "Test privileged {{ jms_account.username }} account" - name: Test privileged account
ansible.builtin.ping: ansible.builtin.ping:
- name: "Check if {{ account.username }} user exists" - name: Change password
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}" password: "{{ account.secret | password_hash('des') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed
ansible.builtin.user:
name: "{{ account.username }}"
when: account.secret_type == "ssh_key"
- name: remove jumpserver ssh key - name: remove jumpserver ssh key
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}" dest: "{{ ssh_params.dest }}"
@@ -52,50 +25,30 @@
- account.secret_type == "ssh_key" - account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms" - ssh_params.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key" - name: Change SSH key
ansible.builtin.authorized_key: ansible.builtin.authorized_key:
user: "{{ account.username }}" user: "{{ account.username }}"
key: "{{ account.secret }}" key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}" exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key" 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 - name: Refresh connection
ansible.builtin.meta: reset_connection ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)" - name: Verify password
ssh_ping: ansible.builtin.ping:
login_user: "{{ account.username }}" become: no
login_password: "{{ account.secret }}" vars:
login_host: "{{ jms_asset.address }}" ansible_user: "{{ account.username }}"
login_port: "{{ jms_asset.port }}" ansible_password: "{{ account.secret }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
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" when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)" - name: Verify SSH key
ssh_ping: ansible.builtin.ping:
login_host: "{{ jms_asset.address }}" become: no
login_port: "{{ jms_asset.port }}" vars:
login_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key" when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -4,58 +4,8 @@ category: host
type: type:
- AIX - AIX
method: change_secret method: change_secret
params:
- name: sudo
type: str
label: 'Sudo'
default: '/bin/whoami'
help_text: "{{ 'Params sudo help text' | trans }}"
- name: shell
type: str
label: 'Shell'
default: '/bin/bash'
- name: home
type: str
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: "{{ 'Params home help text' | trans }}"
- name: groups
type: str
label: "{{ 'Params groups label' | trans }}"
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
i18n: i18n:
AIX account change secret: AIX account change secret:
zh: '使用 Ansible 模块 user 执行账号改密 (DES)' zh: AIX 账号改密
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)' ja: AIX アカウントのパスワード変更
en: 'Using Ansible module user to change account secret (DES)'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
Params home help text:
zh: '默认家目录 /home/{账号用户名}'
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
en: 'Default home directory /home/{account username}'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
en: 'Home'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -1,48 +1,21 @@
- hosts: demo - hosts: demo
gather_facts: no gather_facts: no
tasks: tasks:
- name: "Test privileged {{ jms_account.username }} account" - name: Test privileged account
ansible.builtin.ping: ansible.builtin.ping:
- name: "Check if {{ account.username }} user exists" - name: Change password
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}" password: "{{ account.secret | password_hash('sha512') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed
ansible.builtin.user:
name: "{{ account.username }}"
when: account.secret_type == "ssh_key"
- name: remove jumpserver ssh key - name: remove jumpserver ssh key
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}" dest: "{{ ssh_params.dest }}"
@@ -52,50 +25,30 @@
- account.secret_type == "ssh_key" - account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms" - ssh_params.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key" - name: Change SSH key
ansible.builtin.authorized_key: ansible.builtin.authorized_key:
user: "{{ account.username }}" user: "{{ account.username }}"
key: "{{ account.secret }}" key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}" exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key" 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 - name: Refresh connection
ansible.builtin.meta: reset_connection ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)" - name: Verify password
ssh_ping: ansible.builtin.ping:
login_user: "{{ account.username }}" become: no
login_password: "{{ account.secret }}" vars:
login_host: "{{ jms_asset.address }}" ansible_user: "{{ account.username }}"
login_port: "{{ jms_asset.port }}" ansible_password: "{{ account.secret }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
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" when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)" - name: Verify SSH key
ssh_ping: ansible.builtin.ping:
login_host: "{{ jms_asset.address }}" become: no
login_port: "{{ jms_asset.port }}" vars:
login_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key" when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -5,59 +5,8 @@ type:
- unix - unix
- linux - linux
method: change_secret method: change_secret
params:
- name: sudo
type: str
label: 'Sudo'
default: '/bin/whoami'
help_text: "{{ 'Params sudo help text' | trans }}"
- name: shell
type: str
label: 'Shell'
default: '/bin/bash'
help_text: ''
- name: home
type: str
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: "{{ 'Params home help text' | trans }}"
- name: groups
type: str
label: "{{ 'Params groups label' | trans }}"
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
i18n: i18n:
Posix account change secret: Posix account change secret:
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)' zh: Posix 账号改密
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)' ja: Posix アカウントのパスワード変更
en: 'Using Ansible module user to change account secret (SHA512)'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
Params home help text:
zh: '默认家目录 /home/{账号用户名}'
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
en: 'Default home directory /home/{account username}'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
en: 'Home'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -8,16 +8,19 @@
# debug: # debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}" # msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Get groups of a Windows user
ansible.windows.win_user:
name: "{{ jms_account.username }}"
register: user_info
- name: Change password - name: Change password
ansible.windows.win_user: ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
password_never_expires: yes groups: "{{ user_info.groups[0].name }}"
groups: "{{ params.groups }}"
groups_action: add groups_action: add
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: Refresh connection - name: Refresh connection

View File

@@ -5,22 +5,8 @@ method: change_secret
category: host category: host
type: type:
- windows - windows
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n: i18n:
Windows account change secret: Windows account change secret:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密' zh: Windows 账号改密
ja: 'Ansible win_user モジュールを使用して Windows アカウントのパスワード変更' ja: Windows アカウントのパスワード変更
en: 'Using Ansible module win_user to change Windows account secret'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'

View File

@@ -1,35 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
ansible.windows.win_ping:
# - name: Print variables
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Change password
ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
password_never_expires: yes
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -1,27 +0,0 @@
id: change_secret_windows_rdp_verify
name: "{{ 'Windows account change secret rdp verify' | trans }}"
version: 1
method: change_secret
category: host
type:
- windows
priority: 49
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account change secret rdp verify:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密 RDP 协议测试最后的可连接性'
ja: 'Ansibleモジュールwin_userはWindowsアカウントの改密RDPプロトコルテストの最後の接続性を実行する'
en: 'Using the Ansible module win_user performs Windows account encryption RDP protocol testing for final connectivity'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'

View File

@@ -1,20 +1,20 @@
import os import os
import time import time
from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from openpyxl import Workbook
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, BaseAccountQuerySet from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretFailedMsg from accounts.notifications import ChangeSecretExecutionTaskMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer from accounts.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import HostTypes from assets.const import HostTypes
from common.utils import get_logger from common.utils import get_logger
from common.utils.file import encrypt_and_compress_zip_file from common.utils.file import encrypt_and_compress_zip_file
from common.utils.timezone import local_now_filename from common.utils.timezone import local_now_display
from users.models import User from users.models import User
from ..base.manager import AccountBasePlaybookManager from ..base.manager import AccountBasePlaybookManager
from ...utils import SecretGenerator from ...utils import SecretGenerator
@@ -27,7 +27,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.record_map = self.execution.snapshot.get('record_map', {}) self.method_hosts_mapper = defaultdict(list)
self.secret_type = self.execution.snapshot.get('secret_type') self.secret_type = self.execution.snapshot.get('secret_type')
self.secret_strategy = self.execution.snapshot.get( self.secret_strategy = self.execution.snapshot.get(
'secret_strategy', SecretStrategy.custom 'secret_strategy', SecretStrategy.custom
@@ -50,9 +50,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no' kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
if kwargs['strategy'] == SSHKeyStrategy.set_jms: if kwargs['strategy'] == SSHKeyStrategy.set_jms:
username = account.username kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
path = f'/{username}' if username == "root" else f'/home/{username}'
kwargs['dest'] = f'{path}/.ssh/authorized_keys'
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip()) kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
return kwargs return kwargs
@@ -68,20 +66,20 @@ class ChangeSecretManager(AccountBasePlaybookManager):
else: else:
return self.secret_generator(secret_type).get_secret() return self.secret_generator(secret_type).get_secret()
def get_accounts(self, privilege_account) -> BaseAccountQuerySet | None: def get_accounts(self, privilege_account):
if not privilege_account: if not privilege_account:
print('Not privilege account') print(f'not privilege account')
return return []
asset = privilege_account.asset asset = privilege_account.asset
accounts = asset.accounts.all() accounts = asset.accounts.exclude(username=privilege_account.username)
accounts = accounts.filter(id__in=self.account_ids) accounts = accounts.filter(id__in=self.account_ids)
if self.secret_type: if self.secret_type:
accounts = accounts.filter(secret_type=self.secret_type) accounts = accounts.filter(secret_type=self.secret_type)
if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED: if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED:
accounts = accounts.filter(privileged=False).exclude( accounts = accounts.filter(privileged=False).exclude(
username__in=['root', 'administrator', privilege_account.username] username__in=['root', 'administrator']
) )
return accounts return accounts
@@ -97,49 +95,34 @@ class ChangeSecretManager(AccountBasePlaybookManager):
return host return host
accounts = self.get_accounts(account) accounts = self.get_accounts(account)
error_msg = _("No pending accounts found")
if not accounts: if not accounts:
print(f'{asset}: {error_msg}') print('没有发现待改密账号: %s 用户ID: %s 类型: %s' % (
asset.name, self.account_ids, self.secret_type
))
return [] return []
records = [] method_attr = getattr(automation, self.method_type() + '_method')
method_hosts = self.method_hosts_mapper[method_attr]
method_hosts = [h for h in method_hosts if h != host['name']]
inventory_hosts = [] inventory_hosts = []
records = []
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY: if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
print(f'Windows {asset} does not support ssh key push') print(f'Windows {asset} does not support ssh key push')
return inventory_hosts return inventory_hosts
if asset.type == HostTypes.WINDOWS:
accounts = accounts.filter(secret_type=SecretType.PASSWORD)
host['ssh_params'] = {} host['ssh_params'] = {}
for account in accounts: for account in accounts:
h = deepcopy(host) h = deepcopy(host)
secret_type = account.secret_type secret_type = account.secret_type
h['name'] += '(' + account.username + ')' h['name'] += '(' + account.username + ')'
if self.secret_type is None:
new_secret = account.secret
else:
new_secret = self.get_secret(secret_type) new_secret = self.get_secret(secret_type)
if new_secret is None:
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:
recorder = ChangeSecretRecord( recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution, asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret, old_secret=account.secret, new_secret=new_secret,
) )
records.append(recorder) records.append(recorder)
else:
record_id = self.record_map[asset_account_id]
try:
recorder = ChangeSecretRecord.objects.get(id=record_id)
except ChangeSecretRecord.DoesNotExist:
print(f"Record {record_id} not found")
continue
self.name_recorder_mapper[h['name']] = recorder self.name_recorder_mapper[h['name']] = recorder
private_key_path = None private_key_path = None
@@ -152,13 +135,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
'name': account.name, 'name': account.name,
'username': account.username, 'username': account.username,
'secret_type': secret_type, 'secret_type': secret_type,
'secret': account.escape_jinja2_syntax(new_secret), 'secret': new_secret,
'private_key_path': private_key_path, 'private_key_path': private_key_path
'become': account.get_ansible_become_auth(),
} }
if asset.platform.type == 'oracle': if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None h['account']['mode'] = 'sysdba' if account.privileged else None
inventory_hosts.append(h) inventory_hosts.append(h)
method_hosts.append(h['name'])
self.method_hosts_mapper[method_attr] = method_hosts
ChangeSecretRecord.objects.bulk_create(records) ChangeSecretRecord.objects.bulk_create(records)
return inventory_hosts return inventory_hosts
@@ -166,46 +150,27 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder = self.name_recorder_mapper.get(host) recorder = self.name_recorder_mapper.get(host)
if not recorder: if not recorder:
return return
recorder.status = ChangeSecretRecordStatusChoice.success.value recorder.status = 'success'
recorder.date_finished = timezone.now() recorder.date_finished = timezone.now()
recorder.save()
account = recorder.account account = recorder.account
if not account: if not account:
print("Account not found, deleted ?") print("Account not found, deleted ?")
return return
account.secret = recorder.new_secret account.secret = recorder.new_secret
account.date_updated = timezone.now() account.save(update_fields=['secret'])
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)
def on_host_error(self, host, error, result): def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host) recorder = self.name_recorder_mapper.get(host)
if not recorder: if not recorder:
return return
recorder.status = ChangeSecretRecordStatusChoice.failed.value recorder.status = 'failed'
recorder.date_finished = timezone.now() recorder.date_finished = timezone.now()
recorder.error = error recorder.error = error
try:
recorder.save() recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
def on_runner_failed(self, runner, e): def on_runner_failed(self, runner, e):
logger.error("Account error: ", e) logger.error("Change secret error: ", e)
def check_secret(self): def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \ if self.secret_strategy == SecretStrategy.custom \
@@ -214,72 +179,35 @@ class ChangeSecretManager(AccountBasePlaybookManager):
return False return False
return True return True
@staticmethod
def get_summary(recorders):
total, succeed, failed = 0, 0, 0
for recorder in recorders:
if recorder.status == ChangeSecretRecordStatusChoice.success.value:
succeed += 1
else:
failed += 1
total += 1
summary = _('Success: %s, Failed: %s, Total: %s') % (succeed, failed, total)
return summary
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
if self.secret_type and not self.check_secret(): if not self.check_secret():
self.execution.status = 'success'
self.execution.date_finished = timezone.now()
self.execution.save()
return return
super().run(*args, **kwargs) super().run(*args, **kwargs)
recorders = list(self.name_recorder_mapper.values()) recorders = self.name_recorder_mapper.values()
summary = self.get_summary(recorders) recorders = list(recorders)
print(summary, end='') self.send_recorder_mail(recorders)
if self.record_map:
return
failed_recorders = [
r for r in recorders
if r.status == ChangeSecretRecordStatusChoice.failed.value
]
def send_recorder_mail(self, recorders):
recipients = self.execution.recipients recipients = self.execution.recipients
if not recorders or not recipients:
return
recipients = User.objects.filter(id__in=list(recipients.keys())) 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'] name = self.execution.snapshot['name']
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx') filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx')
if not self.create_file(recorders, filename): if not self.create_file(recorders, filename):
return return
for user in recipients: for user in recipients:
attachments = [] attachments = []
if user.secret_key: if user.secret_key:
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip') password = user.secret_key.encode('utf8')
encrypt_and_compress_zip_file(attachment, user.secret_key, [filename]) attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, [filename])
attachments = [attachment] attachments = [attachment]
ChangeSecretExecutionTaskMsg(name, user, summary).publish(attachments) ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
os.remove(filename) os.remove(filename)
@staticmethod @staticmethod
@@ -294,9 +222,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
rows.insert(0, header) rows.insert(0, header)
wb = Workbook(filename) wb = Workbook(filename)
ws = wb.add_worksheet('Sheet1') ws = wb.create_sheet('Sheet1')
for row_index, row_data in enumerate(rows): for row in rows:
for col_index, col_data in enumerate(row_data): ws.append(row)
ws.write_string(row_index, col_index, col_data) wb.save(filename)
wb.close()
return True return True

View File

@@ -1,9 +1,8 @@
from .backup_account.manager import AccountBackupManager
from .change_secret.manager import ChangeSecretManager
from .gather_accounts.manager import GatherAccountsManager
from .push_account.manager import PushAccountManager from .push_account.manager import PushAccountManager
from .remove_account.manager import RemoveAccountManager from .change_secret.manager import ChangeSecretManager
from .verify_account.manager import VerifyAccountManager from .verify_account.manager import VerifyAccountManager
from .backup_account.manager import AccountBackupManager
from .gather_accounts.manager import GatherAccountsManager
from .verify_gateway_account.manager import VerifyGatewayAccountManager from .verify_gateway_account.manager import VerifyGatewayAccountManager
from ..const import AutomationTypes from ..const import AutomationTypes
@@ -13,7 +12,6 @@ class ExecutionManager:
AutomationTypes.push_account: PushAccountManager, AutomationTypes.push_account: PushAccountManager,
AutomationTypes.change_secret: ChangeSecretManager, AutomationTypes.change_secret: ChangeSecretManager,
AutomationTypes.verify_account: VerifyAccountManager, AutomationTypes.verify_account: VerifyAccountManager,
AutomationTypes.remove_account: RemoveAccountManager,
AutomationTypes.gather_accounts: GatherAccountsManager, AutomationTypes.gather_accounts: GatherAccountsManager,
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager, AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
# TODO 后期迁移到自动化策略中 # TODO 后期迁移到自动化策略中

View File

@@ -1,7 +1,7 @@
- hosts: mongodb - hosts: mongodb
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Get info - name: Get info
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
filter: users filter: users

View File

@@ -1,8 +1,7 @@
- hosts: mysql - hosts: mysql
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks: tasks:
- name: Get info - name: Get info
@@ -11,10 +10,6 @@
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: users filter: users
register: db_info register: db_info

View File

@@ -1,7 +1,7 @@
- hosts: oralce - hosts: oralce
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Get info - name: Get info

View File

@@ -1,7 +1,7 @@
- hosts: postgresql - hosts: postgresql
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Get info - name: Get info

View File

@@ -1,5 +1,3 @@
import re
from django.utils import timezone from django.utils import timezone
__all__ = ['GatherAccountsFilter'] __all__ = ['GatherAccountsFilter']
@@ -15,8 +13,8 @@ class GatherAccountsFilter:
def mysql_filter(info): def mysql_filter(info):
result = {} result = {}
for _, user_dict in info.items(): for _, user_dict in info.items():
for username, _ in user_dict.items(): for username, data in user_dict.items():
if len(username.split('.')) == 1: if data.get('account_locked') == 'N':
result[username] = {} result[username] = {}
return result return result
@@ -29,25 +27,18 @@ class GatherAccountsFilter:
@staticmethod @staticmethod
def posix_filter(info): def posix_filter(info):
username_pattern = re.compile(r'^(\S+)')
ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
login_time_pattern = re.compile(r'\w{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}')
result = {} result = {}
for line in info: for line in info:
usernames = username_pattern.findall(line) data = line.split('@')
username = ''.join(usernames) if len(data) == 1:
if username: result[line] = {}
result[username] = {}
else:
continue continue
ip_addrs = ip_pattern.findall(line)
ip_addr = ''.join(ip_addrs) if len(data) != 3:
if ip_addr: continue
result[username].update({'address': ip_addr}) username, address, dt = data
login_times = login_time_pattern.findall(line) date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z')
if login_times: result[username] = {'address': address, 'date': date}
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
result[username].update({'date': date})
return result return result
@staticmethod @staticmethod

View File

@@ -5,7 +5,7 @@
ansible.builtin.shell: ansible.builtin.shell:
cmd: > cmd: >
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users; users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $0 }') do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }')
if [ -n "$k" ]; then if [ -n "$k" ]; then
echo $k echo $k
else else

View File

@@ -8,6 +8,5 @@ method: gather_accounts
i18n: i18n:
Posix account gather: Posix account gather:
zh: 使用命令 getent passwd 收集 Posix 资产账号 zh: Posix 账号收集
ja: コマンド getent を使用してアセットアカウント収集する ja: Posix アカウント収集
en: Using command getent to gather accounts

View File

@@ -1,10 +1,9 @@
- hosts: demo - hosts: demo
gather_facts: no gather_facts: no
tasks: tasks:
- name: Gather windows account - name: Gather posix account
ansible.builtin.win_shell: net user ansible.builtin.win_shell: net user
register: result register: result
ignore_errors: true
- name: Define info by set_fact - name: Define info by set_fact
ansible.builtin.set_fact: ansible.builtin.set_fact:

View File

@@ -8,6 +8,5 @@ type:
i18n: i18n:
Windows account gather: Windows account gather:
zh: 使用命令 net user 收集 Windows 账号 zh: Windows 账号收集
ja: コマンド net user を使用して Windows アカウント収集する ja: Windows アカウント収集
en: Using command net user to gather accounts

View File

@@ -1,14 +1,9 @@
from collections import defaultdict
from accounts.const import AutomationTypes from accounts.const import AutomationTypes
from accounts.models import GatheredAccount from accounts.models import GatheredAccount
from assets.models import Asset
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from users.models import User
from .filter import GatherAccountsFilter from .filter import GatherAccountsFilter
from ..base.manager import AccountBasePlaybookManager from ..base.manager import AccountBasePlaybookManager
from ...notifications import GatherAccountChangeMsg
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -17,9 +12,6 @@ class GatherAccountsManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.host_asset_mapper = {} self.host_asset_mapper = {}
self.asset_account_info = {}
self.asset_username_mapper = defaultdict(set)
self.is_sync_account = self.execution.snapshot.get('is_sync_account') self.is_sync_account = self.execution.snapshot.get('is_sync_account')
@classmethod @classmethod
@@ -34,11 +26,10 @@ class GatherAccountsManager(AccountBasePlaybookManager):
def filter_success_result(self, tp, result): def filter_success_result(self, tp, result):
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result) result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
return result return result
@staticmethod
def generate_data(self, asset, result): def generate_data(asset, result):
data = [] data = []
for username, info in result.items(): for username, info in result.items():
self.asset_username_mapper[str(asset.id)].add(username)
d = {'asset': asset, 'username': username, 'present': True} d = {'asset': asset, 'username': username, 'present': True}
if info.get('date'): if info.get('date'):
d['date_last_login'] = info['date'] d['date_last_login'] = info['date']
@@ -47,29 +38,8 @@ class GatherAccountsManager(AccountBasePlaybookManager):
data.append(d) data.append(d)
return data return data
def collect_asset_account_info(self, asset, result): def update_or_create_accounts(self, asset, result):
data = self.generate_data(asset, result) 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')
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')
def update_or_create_accounts(self):
for asset, data in self.asset_account_info.items():
with tmp_to_org(asset.org_id): with tmp_to_org(asset.org_id):
gathered_accounts = [] gathered_accounts = []
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False) GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
@@ -80,60 +50,14 @@ class GatherAccountsManager(AccountBasePlaybookManager):
) )
gathered_accounts.append(gathered_account) gathered_accounts.append(gathered_account)
if not self.is_sync_account: if not self.is_sync_account:
continue return
GatheredAccount.sync_accounts(gathered_accounts) GatheredAccount.sync_accounts(gathered_accounts)
def run(self, *args, **kwargs): def on_host_success(self, host, result):
super().run(*args, **kwargs) info = result.get('debug', {}).get('res', {}).get('info', {})
users, change_info = self.generate_send_users_and_change_info() asset = self.host_asset_mapper.get(host)
self.update_or_create_accounts() if asset and info:
self.send_email_if_need(users, change_info) result = self.filter_success_result(asset.type, info)
self.update_or_create_accounts(asset, result)
def generate_send_users_and_change_info(self): else:
recipients = self.execution.recipients logger.error("Not found info".format(host))
if not self.asset_username_mapper or not recipients:
return None, None
users = User.objects.filter(id__in=recipients)
if not users:
return users, None
asset_ids = self.asset_username_mapper.keys()
assets = Asset.objects.filter(id__in=asset_ids)
gather_accounts = GatheredAccount.objects.filter(asset_id__in=asset_ids, present=True)
asset_id_map = {str(asset.id): asset for asset in assets}
asset_id_username = list(assets.values_list('id', 'accounts__username'))
asset_id_username.extend(list(gather_accounts.values_list('asset_id', 'username')))
system_asset_username_mapper = defaultdict(set)
for asset_id, username in asset_id_username:
system_asset_username_mapper[str(asset_id)].add(username)
change_info = {}
for asset_id, usernames in self.asset_username_mapper.items():
system_usernames = system_asset_username_mapper.get(asset_id)
if not system_usernames:
continue
add_usernames = usernames - system_usernames
remove_usernames = system_usernames - usernames
k = f'{asset_id_map[asset_id]}[{asset_id}]'
if not add_usernames and not remove_usernames:
continue
change_info[k] = {
'add_usernames': ', '.join(add_usernames),
'remove_usernames': ', '.join(remove_usernames),
}
return users, change_info
@staticmethod
def send_email_if_need(users, change_info):
if not users or not change_info:
return
for user in users:
GatherAccountChangeMsg(user, change_info).publish_async()

View File

@@ -1,7 +1,7 @@
- hosts: mongodb - hosts: mongodb
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test MongoDB connection - name: Test MongoDB connection
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info register: db_info
@@ -31,15 +31,15 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}" db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded when: db_info is succeeded
register: change_info
- name: Verify password - name: Verify password
mongodb_ping: mongodb_ping:
@@ -49,7 +49,10 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}" ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}" ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -7,6 +7,5 @@ method: push_account
i18n: i18n:
MongoDB account push: MongoDB account push:
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号推送 zh: MongoDB 账号推送
ja: Ansible mongodb モジュールを使用してアカウントプッシュする ja: MongoDB アカウントプッシュ
en: Using Ansible module mongodb to push account

View File

@@ -1,9 +1,8 @@
- hosts: mysql - hosts: mysql
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}" db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks: tasks:
- name: Test MySQL connection - name: Test MySQL connection
@@ -12,10 +11,6 @@
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version filter: version
register: db_info register: db_info
@@ -29,16 +24,12 @@
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
host: "%" host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}" priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded when: db_info is succeeded
register: change_info
- name: Verify password - name: Verify password
community.mysql.mysql_info: community.mysql.mysql_info:
@@ -46,8 +37,7 @@
login_password: "{{ account.secret }}" login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version filter: version
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -8,6 +8,5 @@ method: push_account
i18n: i18n:
MySQL account push: MySQL account push:
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号推送 zh: MySQL 账号推送
ja: Ansible mysql モジュールを使用してアカウントプッシュする ja: MySQL アカウントプッシュ
en: Using Ansible module mysql to push account

View File

@@ -1,7 +1,7 @@
- hosts: oracle - hosts: oracle
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test Oracle connection - name: Test Oracle connection
@@ -29,8 +29,8 @@
mode: "{{ jms_account.mode }}" mode: "{{ jms_account.mode }}"
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded when: db_info is succeeded
register: change_info
- name: Verify password - name: Verify password
oracle_ping: oracle_ping:
@@ -39,4 +39,6 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}" login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}" when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -7,6 +7,5 @@ method: push_account
i18n: i18n:
Oracle account push: Oracle account push:
zh: 使用 Python 模块 oracledb 执行 Oracle 账号推送 zh: Oracle 账号推送
ja: Python oracledb モジュールを使用してアカウントプッシュする ja: Oracle アカウントプッシュ
en: Using Python module oracledb to push account

View File

@@ -1,7 +1,7 @@
- hosts: postgre - hosts: postgre
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test PostgreSQL connection - name: Test PostgreSQL connection
@@ -29,7 +29,6 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
role_attr_flags: LOGIN role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded when: result is succeeded
register: change_info register: change_info

View File

@@ -7,6 +7,5 @@ method: push_account
i18n: i18n:
PostgreSQL account push: PostgreSQL account push:
zh: 使用 Ansible 模块 postgresql 执行 PostgreSQL 账号推送 zh: PostgreSQL 账号推送
ja: Ansible postgresql モジュールを使用してアカウントプッシュする ja: PostgreSQL アカウントプッシュ
en: Using Ansible module postgresql to push account

View File

@@ -1,7 +1,7 @@
- hosts: sqlserver - hosts: sqlserver
gather_facts: no gather_facts: no
vars: vars:
ansible_python_interpreter: /opt/py3/bin/python ansible_python_interpreter: /usr/local/bin/python
tasks: tasks:
- name: Test SQLServer connection - name: Test SQLServer connection
@@ -40,8 +40,7 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version" script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0 when: user_exist.query_results[0] | length != 0
register: change_info register: change_info
@@ -52,8 +51,7 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version" script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0 when: user_exist.query_results[0] | length == 0
register: change_info register: change_info
@@ -66,3 +64,6 @@
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: | script: |
SELECT @@version SELECT @@version
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -7,6 +7,5 @@ method: push_account
i18n: i18n:
SQLServer account push: SQLServer account push:
zh: 使用 Ansible 模块 mssql 执行 SQLServer 账号推送 zh: SQLServer 账号推送
ja: Ansible mssql モジュールを使用してアカウントプッシュする ja: SQLServer アカウントプッシュ
en: Using Ansible module mssql to push account

View File

@@ -1,46 +1,48 @@
- hosts: demo - hosts: demo
gather_facts: no gather_facts: no
tasks: tasks:
- name: "Test privileged {{ jms_account.username }} account" - name: Test privileged account
ansible.builtin.ping: ansible.builtin.ping:
- name: "Check if {{ account.username }} user exists" - name: Push user
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
shell: "{{ params.shell }}" shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}" home: "{{ '/home/' + account.username }}"
groups: "{{ params.groups }}" groups: "{{ params.groups }}"
expires: -1 expires: -1
state: present state: present
when: user_info.failed
- name: "Add {{ account.username }} group" - name: "Add {{ account.username }} group"
ansible.builtin.group: ansible.builtin.group:
name: "{{ account.username }}" name: "{{ account.username }}"
state: present state: present
when: user_info.failed
- name: "Add {{ account.username }} user to group" - name: Check home dir exists
ansible.builtin.stat:
path: "{{ '/home/' + account.username }}"
register: home_existed
- name: Set home dir permission
ansible.builtin.file:
path: "{{ '/home/' + account.username }}"
owner: "{{ account.username }}"
group: "{{ account.username }}"
mode: "0700"
when:
- home_existed.stat.exists == true
- name: Add user groups
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
groups: "{{ params.groups }}" groups: "{{ params.groups }}"
when: when: params.groups
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password" - name: Push user password
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}" password: "{{ account.secret | password_hash('sha512') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: remove jumpserver ssh key - name: remove jumpserver ssh key
@@ -52,14 +54,14 @@
- account.secret_type == "ssh_key" - account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms" - ssh_params.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key" - name: Push SSH key
ansible.builtin.authorized_key: ansible.builtin.authorized_key:
user: "{{ account.username }}" user: "{{ account.username }}"
key: "{{ account.secret }}" key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}" exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key" when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting" - name: Set sudo setting
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
dest: /etc/sudoers dest: /etc/sudoers
state: present state: present
@@ -67,36 +69,25 @@
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s validate: visudo -cf %s
when: when:
- user_info.failed
- params.sudo - params.sudo
- name: Refresh connection - name: Refresh connection
ansible.builtin.meta: reset_connection ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)" - name: Verify password
ssh_ping: ansible.builtin.ping:
login_user: "{{ account.username }}" become: no
login_password: "{{ account.secret }}" vars:
login_host: "{{ jms_asset.address }}" ansible_user: "{{ account.username }}"
login_port: "{{ jms_asset.port }}" ansible_password: "{{ account.secret }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
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" when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)" - name: Verify SSH key
ssh_ping: ansible.builtin.ping:
login_host: "{{ jms_asset.address }}" become: no
login_port: "{{ jms_asset.port }}" vars:
login_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key" when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -9,53 +9,21 @@ params:
type: str type: str
label: 'Sudo' label: 'Sudo'
default: '/bin/whoami' default: '/bin/whoami'
help_text: "{{ 'Params sudo help text' | trans }}" help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
- name: shell - name: shell
type: str type: str
label: 'Shell' label: 'Shell'
default: '/bin/bash' default: '/bin/bash'
- name: home
type: str
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: "{{ 'Params home help text' | trans }}"
- name: groups - name: groups
type: str type: str
label: "{{ 'Params groups label' | trans }}" label: '用户组'
default: '' default: ''
help_text: "{{ 'Params groups help text' | trans }}" help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n: i18n:
Aix account push: Aix account push:
zh: '使用 Ansible 模块 user 执行 Aix 账号推送 (DES)' zh: Aix 账号推送
ja: 'Ansible user モジュールを使用して Aix アカウントプッシュする (DES)' ja: Aix アカウントプッシュ
en: 'Using Ansible module user to push account (DES)'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
Params home help text:
zh: '默认家目录 /home/{账号用户名}'
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
en: 'Default home directory /home/{account username}'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
en: 'Home'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -1,46 +1,48 @@
- hosts: demo - hosts: demo
gather_facts: no gather_facts: no
tasks: tasks:
- name: "Test privileged {{ jms_account.username }} account" - name: Test privileged account
ansible.builtin.ping: ansible.builtin.ping:
- name: "Check if {{ account.username }} user exists" - name: Push user
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
shell: "{{ params.shell }}" shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}" home: "{{ '/home/' + account.username }}"
groups: "{{ params.groups }}" groups: "{{ params.groups }}"
expires: -1 expires: -1
state: present state: present
when: user_info.failed
- name: "Add {{ account.username }} group" - name: "Add {{ account.username }} group"
ansible.builtin.group: ansible.builtin.group:
name: "{{ account.username }}" name: "{{ account.username }}"
state: present state: present
when: user_info.failed
- name: "Add {{ account.username }} user to group" - name: Check home dir exists
ansible.builtin.stat:
path: "{{ '/home/' + account.username }}"
register: home_existed
- name: Set home dir permission
ansible.builtin.file:
path: "{{ '/home/' + account.username }}"
owner: "{{ account.username }}"
group: "{{ account.username }}"
mode: "0700"
when:
- home_existed.stat.exists == true
- name: Add user groups
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
groups: "{{ params.groups }}" groups: "{{ params.groups }}"
when: when: params.groups
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password" - name: Push user password
ansible.builtin.user: ansible.builtin.user:
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}" password: "{{ account.secret | password_hash('sha512') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: remove jumpserver ssh key - name: remove jumpserver ssh key
@@ -52,14 +54,14 @@
- account.secret_type == "ssh_key" - account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms" - ssh_params.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key" - name: Push SSH key
ansible.builtin.authorized_key: ansible.builtin.authorized_key:
user: "{{ account.username }}" user: "{{ account.username }}"
key: "{{ account.secret }}" key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}" exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key" when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting" - name: Set sudo setting
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
dest: /etc/sudoers dest: /etc/sudoers
state: present state: present
@@ -67,36 +69,25 @@
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s validate: visudo -cf %s
when: when:
- user_info.failed
- params.sudo - params.sudo
- name: Refresh connection - name: Refresh connection
ansible.builtin.meta: reset_connection ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)" - name: Verify password
ssh_ping: ansible.builtin.ping:
login_user: "{{ account.username }}" become: no
login_password: "{{ account.secret }}" vars:
login_host: "{{ jms_asset.address }}" ansible_user: "{{ account.username }}"
login_port: "{{ jms_asset.port }}" ansible_password: "{{ account.secret }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
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" when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)" - name: Verify SSH key
ssh_ping: ansible.builtin.ping:
login_host: "{{ jms_asset.address }}" become: no
login_port: "{{ jms_asset.port }}" vars:
login_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key" when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -10,7 +10,7 @@ params:
type: str type: str
label: 'Sudo' label: 'Sudo'
default: '/bin/whoami' default: '/bin/whoami'
help_text: "{{ 'Params sudo help text' | trans }}" help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
- name: shell - name: shell
type: str type: str
@@ -18,45 +18,13 @@ params:
default: '/bin/bash' default: '/bin/bash'
help_text: '' help_text: ''
- name: home
type: str
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: "{{ 'Params home help text' | trans }}"
- name: groups - name: groups
type: str type: str
label: "{{ 'Params groups label' | trans }}" label: '用户组'
default: '' default: ''
help_text: "{{ 'Params groups help text' | trans }}" help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n: i18n:
Posix account push: Posix account push:
zh: '使用 Ansible 模块 user 执行账号推送 (sha512)' zh: Posix 账号推送
ja: 'Ansible user モジュールを使用してアカウントプッシュする (sha512)' ja: Posix アカウントプッシュ
en: 'Using Ansible module user to push account (sha512)'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
Params home help text:
zh: '默认家目录 /home/{账号用户名}'
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
en: 'Default home directory /home/{account username}'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
en: 'Home'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -17,7 +17,6 @@
groups: "{{ params.groups }}" groups: "{{ params.groups }}"
groups_action: add groups_action: add
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: Refresh connection - name: Refresh connection

View File

@@ -10,15 +10,9 @@ params:
type: str type: str
label: '用户组' label: '用户组'
default: 'Users,Remote Desktop Users' default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}" help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n: i18n:
Windows account push: Windows account push:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送' zh: Windows 账号推送
ja: 'Ansible win_user モジュールを使用して Windows アカウントプッシュする' ja: Windows アカウントプッシュ
en: 'Using Ansible module win_user to push account'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'

View File

@@ -1,35 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
ansible.windows.win_ping:
# - name: Print variables
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Push user password
ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
password_never_expires: yes
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -1,25 +0,0 @@
id: push_account_windows_rdp_verify
name: "{{ 'Windows account push rdp verify' | trans }}"
version: 1
method: push_account
category: host
type:
- windows
priority: 49
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account push rdp verify:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送(最后使用 Python 模块 pyfreerdp 验证账号的可连接性)'
ja: 'Ansible モジュール win_user を使用して Windows アカウントのプッシュを実行します (最後に Python モジュール pyfreerdp を使用してアカウントの接続性を確認します)'
en: 'Use the Ansible module win_user to perform Windows account push (finally use the Python module pyfreerdp to verify the connectability of the account)'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'

View File

@@ -1,4 +1,7 @@
from accounts.const import AutomationTypes from copy import deepcopy
from accounts.const import AutomationTypes, SecretType, Connectivity
from assets.const import HostTypes
from common.utils import get_logger from common.utils import get_logger
from ..base.manager import AccountBasePlaybookManager from ..base.manager import AccountBasePlaybookManager
from ..change_secret.manager import ChangeSecretManager from ..change_secret.manager import ChangeSecretManager
@@ -7,11 +10,83 @@ logger = get_logger(__name__)
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager): class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
ansible_account_prefer = ''
@classmethod @classmethod
def method_type(cls): def method_type(cls):
return AutomationTypes.push_account return AutomationTypes.push_account
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super(ChangeSecretManager, self).host_callback(
host, asset=asset, account=account, automation=automation,
path_dir=path_dir, **kwargs
)
if host.get('error'):
return host
accounts = self.get_accounts(account)
inventory_hosts = []
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
msg = f'Windows {asset} does not support ssh key push'
print(msg)
return inventory_hosts
host['ssh_params'] = {}
for account in accounts:
h = deepcopy(host)
secret_type = account.secret_type
h['name'] += '(' + account.username + ')'
if self.secret_type is None:
new_secret = account.secret
else:
new_secret = self.get_secret(secret_type)
self.name_recorder_mapper[h['name']] = {
'account': account, 'new_secret': new_secret,
}
private_key_path = None
if secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(new_secret, path_dir)
new_secret = self.generate_public_key(new_secret)
h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type))
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': secret_type,
'secret': new_secret,
'private_key_path': private_key_path
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
account_info = self.name_recorder_mapper.get(host)
if not account_info:
return
account = account_info['account']
new_secret = account_info['new_secret']
if not account:
return
account.secret = new_secret
account.save(update_fields=['secret'])
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result):
pass
def on_runner_failed(self, runner, e):
logger.error("Pust account error: ", e)
def run(self, *args, **kwargs):
if self.secret_type and not self.check_secret():
return
super(ChangeSecretManager, self).run(*args, **kwargs)
# @classmethod # @classmethod
# def trigger_by_asset_create(cls, asset): # def trigger_by_asset_create(cls, asset):
# automations = PushAccountAutomation.objects.filter( # automations = PushAccountAutomation.objects.filter(

View File

@@ -1,21 +0,0 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
mongodb_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
state: absent

View File

@@ -1,12 +0,0 @@
id: remove_account_mongodb
name: "{{ 'MongoDB account remove' | trans }}"
category: database
type:
- mongodb
method: remove_account
i18n:
MongoDB account remove:
zh: 使用 Ansible 模块 mongodb 删除账号
ja: Ansible モジュール mongodb を使用してアカウントを削除する
en: Delete account using Ansible module mongodb

View File

@@ -1,19 +0,0 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks:
- name: "Remove account"
community.mysql.mysql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
name: "{{ account.username }}"
state: absent

View File

@@ -1,14 +0,0 @@
id: remove_account_mysql
name: "{{ 'MySQL account remove' | trans }}"
category: database
type:
- mysql
- mariadb
method: remove_account
i18n:
MySQL account remove:
zh: 使用 Ansible 模块 mysql_user 删除账号
ja: Ansible モジュール mysql_user を使用してアカウントを削除します
en: Use the Ansible module mysql_user to delete the account

View File

@@ -1,16 +0,0 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
oracle_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
state: absent

View File

@@ -1,12 +0,0 @@
id: remove_account_oracle
name: "{{ 'Oracle account remove' | trans }}"
category: database
type:
- oracle
method: remove_account
i18n:
Oracle account remove:
zh: 使用 Python 模块 oracledb 删除账号
ja: Python モジュール oracledb を使用してアカウントを検証する
en: Using Python module oracledb to verify account

View File

@@ -1,15 +0,0 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
community.postgresql.postgresql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
state: absent

View File

@@ -1,12 +0,0 @@
id: remove_account_postgresql
name: "{{ 'PostgreSQL account remove' | trans }}"
category: database
type:
- postgresql
method: remove_account
i18n:
PostgreSQL account remove:
zh: 使用 Ansible 模块 postgresql_user 删除账号
ja: Ansible モジュール postgresql_user を使用してアカウントを削除します
en: Use the Ansible module postgresql_user to delete the account

View File

@@ -1,14 +0,0 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: "{{ jms_asset.spec_info.db_name }}"
script: "DROP USER {{ account.username }}"

View File

@@ -1,12 +0,0 @@
id: remove_account_sqlserver
name: "{{ 'SQLServer account remove' | trans }}"
category: database
type:
- sqlserver
method: remove_account
i18n:
SQLServer account remove:
zh: 使用 Ansible 模块 mssql 删除账号
ja: Ansible モジュール mssql を使用してアカウントを削除する
en: Use Ansible module mssql to delete account

View File

@@ -1,28 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: "Get user home directory path"
ansible.builtin.shell:
cmd: "getent passwd {{ account.username }} | cut -d: -f6"
register: user_home_dir
ignore_errors: yes
- name: "Check if user home directory exists"
ansible.builtin.stat:
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:
name: "{{ account.username }}"
state: absent
remove: "{{ home_dir.stat.exists }}"
when: home_dir.stat | default(false)

View File

@@ -1,13 +0,0 @@
id: remove_account_posix
name: "{{ 'Posix account remove' | trans }}"
category: host
type:
- linux
- unix
method: remove_account
i18n:
Posix account remove:
zh: 使用 Ansible 模块 user 删除账号
ja: Ansible モジュール ユーザーを使用してアカウントを削除します
en: Use the Ansible module user to delete the account

View File

@@ -1,7 +0,0 @@
- hosts: windows
gather_facts: no
tasks:
- name: "Remove account"
ansible.windows.win_user:
name: "{{ account.username }}"
state: absent

View File

@@ -1,13 +0,0 @@
id: remove_account_windows
name: "{{ 'Windows account remove' | trans }}"
version: 1
method: remove_account
category: host
type:
- windows
i18n:
Windows account remove:
zh: 使用 Ansible 模块 win_user 删除账号
ja: Ansible モジュール win_user を使用してアカウントを削除する
en: Use the Ansible module win_user to delete an account

View File

@@ -1,70 +0,0 @@
import os
from copy import deepcopy
from django.db.models import QuerySet
from accounts.const import AutomationTypes
from accounts.models import Account
from common.utils import get_logger
from ..base.manager import AccountBasePlaybookManager
logger = get_logger(__name__)
class RemoveAccountManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_account_mapper = {}
def prepare_runtime_dir(self):
path = super().prepare_runtime_dir()
ansible_config_path = os.path.join(path, 'ansible.cfg')
with open(ansible_config_path, 'w') as f:
f.write('[ssh_connection]\n')
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
return path
@classmethod
def method_type(cls):
return AutomationTypes.remove_account
def get_gather_accounts(self, privilege_account, gather_accounts: QuerySet):
gather_account_ids = self.execution.snapshot['gather_accounts']
gather_accounts = gather_accounts.filter(id__in=gather_account_ids)
gather_accounts = gather_accounts.exclude(
username__in=[privilege_account.username, 'root', 'Administrator']
)
return gather_accounts
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
if host.get('error'):
return host
gather_accounts = asset.gatheredaccount_set.all()
gather_accounts = self.get_gather_accounts(account, gather_accounts)
inventory_hosts = []
for gather_account in gather_accounts:
h = deepcopy(host)
h['name'] += '(' + gather_account.username + ')'
self.host_account_mapper[h['name']] = (asset, gather_account)
h['account'] = {'username': gather_account.username}
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
tuple_asset_gather_account = self.host_account_mapper.get(host)
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')

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