mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-16 09:02:49 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4cf81d96c | ||
|
|
a098bc6c06 | ||
|
|
86870ad985 | ||
|
|
c602bf7224 | ||
|
|
86dab4fc6e | ||
|
|
a85a80a945 | ||
|
|
349edc10aa | ||
|
|
44918e3cb5 | ||
|
|
9a2f6c0d70 | ||
|
|
934969a8f1 | ||
|
|
57162c1628 | ||
|
|
32fb36867f | ||
|
|
158b589028 | ||
|
|
d64277353c | ||
|
|
bff6f397ce | ||
|
|
0ad461a804 | ||
|
|
a1dcef0ba0 | ||
|
|
dbb1ee3a75 | ||
|
|
d6bd207a17 | ||
|
|
e69ba27ff4 | ||
|
|
adbe7c07c6 | ||
|
|
d1eacf53d4 |
@@ -1,4 +1,5 @@
|
|||||||
.git
|
.git
|
||||||
|
logs/*
|
||||||
data/*
|
data/*
|
||||||
.github
|
.github
|
||||||
tmp/*
|
tmp/*
|
||||||
@@ -7,4 +8,4 @@ celerybeat.pid
|
|||||||
### Vagrant ###
|
### Vagrant ###
|
||||||
.vagrant/
|
.vagrant/
|
||||||
apps/xpack/.git
|
apps/xpack/.git
|
||||||
.history/
|
|
||||||
|
|||||||
33
.github/ISSUE_TEMPLATE/----.md
vendored
33
.github/ISSUE_TEMPLATE/----.md
vendored
@@ -1,35 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: 需求建议
|
name: 需求建议
|
||||||
about: 提出针对本项目的想法和建议
|
about: 提出针对本项目的想法和建议
|
||||||
title: "[Feature] 需求标题"
|
title: "[Feature] "
|
||||||
labels: 类型:需求
|
labels: 类型:需求
|
||||||
assignees:
|
assignees: ibuler
|
||||||
- ibuler
|
|
||||||
- baijiangjie
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 注意
|
**请描述您的需求或者改进建议.**
|
||||||
_针对过于简单的需求描述不予考虑。请确保提供足够的细节和信息以支持功能的开发和实现。_
|
|
||||||
|
|
||||||
## 功能名称
|
|
||||||
[在这里输入功能的名称或标题]
|
|
||||||
|
|
||||||
## 功能描述
|
|
||||||
[在这里描述该功能的详细内容,包括其作用、目的和所需的功能]
|
|
||||||
|
|
||||||
## 用户故事(可选)
|
|
||||||
[如果适用,可以提供用户故事来更好地理解该功能的使用场景和用户期望]
|
|
||||||
|
|
||||||
## 功能要求
|
|
||||||
- [要求1:描述该功能的具体要求,如界面设计、交互逻辑等]
|
|
||||||
- [要求2:描述该功能的另一个具体要求]
|
|
||||||
- [以此类推,列出所有相关的功能要求]
|
|
||||||
|
|
||||||
## 示例或原型(可选)
|
|
||||||
[如果有的话,提供该功能的示例或原型图以更好地说明功能的实现方式]
|
|
||||||
|
|
||||||
## 优先级
|
|
||||||
[描述该功能的优先级,如高、中、低,或使用数字等其他标识]
|
|
||||||
|
|
||||||
## 备注(可选)
|
|
||||||
[在这里添加任何其他相关信息或备注]
|
|
||||||
|
|||||||
51
.github/ISSUE_TEMPLATE/bug---.md
vendored
51
.github/ISSUE_TEMPLATE/bug---.md
vendored
@@ -1,51 +1,22 @@
|
|||||||
---
|
---
|
||||||
name: Bug 提交
|
name: Bug 提交
|
||||||
about: 提交产品缺陷帮助我们更好的改进
|
about: 提交产品缺陷帮助我们更好的改进
|
||||||
title: "[Bug] Bug 标题"
|
title: "[Bug] "
|
||||||
labels: 类型:Bug
|
labels: 类型:bug
|
||||||
assignees:
|
assignees: wojiushixiaobai
|
||||||
- baijiangjie
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 注意
|
**JumpServer 版本(v1.5.9以下不再支持)**
|
||||||
**JumpServer 版本( v2.28 之前的版本不再支持 )** <br>
|
|
||||||
_针对过于简单的 Bug 描述不予考虑。请确保提供足够的细节和信息以支持 Bug 的复现和修复。_
|
|
||||||
|
|
||||||
## 当前使用的 JumpServer 版本 (必填)
|
|
||||||
[在这里输入当前使用的 JumpServer 的版本号]
|
|
||||||
|
|
||||||
## 使用的版本类型 (必填)
|
|
||||||
- [ ] 社区版
|
|
||||||
- [ ] 企业版
|
|
||||||
- [ ] 企业试用版
|
|
||||||
|
|
||||||
|
|
||||||
## 版本安装方式 (必填)
|
**浏览器版本**
|
||||||
- [ ] 在线安装 (一键命令)
|
|
||||||
- [ ] 离线安装 (下载离线包)
|
|
||||||
- [ ] All-in-One
|
|
||||||
- [ ] 1Panel 安装
|
|
||||||
- [ ] Kubernetes 安装
|
|
||||||
- [ ] 源码安装
|
|
||||||
|
|
||||||
## Bug 描述 (详细)
|
|
||||||
[在这里描述 Bug 的详细情况,包括其影响和出现的具体情况]
|
|
||||||
|
|
||||||
## 复现步骤
|
**Bug 描述**
|
||||||
1. [描述如何复现 Bug 的第一步]
|
|
||||||
2. [描述如何复现 Bug 的第二步]
|
|
||||||
3. [以此类推,列出所有复现 Bug 所需的步骤]
|
|
||||||
|
|
||||||
## 期望行为
|
|
||||||
[描述 Bug 出现时期望的系统行为或结果]
|
|
||||||
|
|
||||||
## 实际行为
|
**Bug 重现步骤(有截图更好)**
|
||||||
[描述实际上发生了什么,以及 Bug 出现的具体情况]
|
1.
|
||||||
|
2.
|
||||||
## 系统环境
|
3.
|
||||||
- 操作系统:[例如:Windows 10, macOS Big Sur]
|
|
||||||
- 浏览器/应用版本:[如果适用,请提供相关版本信息]
|
|
||||||
- 其他相关环境信息:[如果有其他相关环境信息,请在此处提供]
|
|
||||||
|
|
||||||
## 附加信息(可选)
|
|
||||||
[在这里添加任何其他相关信息,如截图、错误信息等]
|
|
||||||
|
|||||||
48
.github/ISSUE_TEMPLATE/question.md
vendored
48
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,50 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: 问题咨询
|
name: 问题咨询
|
||||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||||
title: "[Question] 问题标题"
|
title: "[Question] "
|
||||||
labels: 类型:提问
|
labels: 类型:提问
|
||||||
assignees:
|
assignees: wojiushixiaobai
|
||||||
- baijiangjie
|
|
||||||
---
|
---
|
||||||
## 注意
|
|
||||||
**请描述您的问题.** <br>
|
|
||||||
**JumpServer 版本( v2.28 之前的版本不再支持 )** <br>
|
|
||||||
_针对过于简单的 Bug 描述不予考虑。请确保提供足够的细节和信息以支持 Bug 的复现和修复。_
|
|
||||||
|
|
||||||
## 当前使用的 JumpServer 版本 (必填)
|
|
||||||
[在这里输入当前使用的 JumpServer 的版本号]
|
|
||||||
|
|
||||||
## 使用的版本类型 (必填)
|
|
||||||
- [ ] 社区版
|
|
||||||
- [ ] 企业版
|
|
||||||
- [ ] 企业试用版
|
|
||||||
|
|
||||||
|
|
||||||
## 版本安装方式 (必填)
|
|
||||||
- [ ] 在线安装 (一键命令)
|
|
||||||
- [ ] 离线安装 (下载离线包)
|
|
||||||
- [ ] All-in-One
|
|
||||||
- [ ] 1Panel 安装
|
|
||||||
- [ ] Kubernetes 安装
|
|
||||||
- [ ] 源码安装
|
|
||||||
|
|
||||||
## 问题描述 (详细)
|
|
||||||
[在这里描述你遇到的问题]
|
|
||||||
|
|
||||||
## 背景信息
|
|
||||||
- 操作系统:[例如:Windows 10, macOS Big Sur]
|
|
||||||
- 浏览器/应用版本:[如果适用,请提供相关版本信息]
|
|
||||||
- 其他相关环境信息:[如果有其他相关环境信息,请在此处提供]
|
|
||||||
|
|
||||||
## 具体问题
|
|
||||||
[在这里详细描述你的问题,包括任何相关细节或错误信息]
|
|
||||||
|
|
||||||
## 尝试过的解决方法
|
|
||||||
[如果你已经尝试过解决问题,请在这里列出你已经尝试过的解决方法]
|
|
||||||
|
|
||||||
## 预期结果
|
|
||||||
[描述你期望的解决方案或结果]
|
|
||||||
|
|
||||||
## 我们的期望
|
|
||||||
[描述你希望我们提供的帮助或支持]
|
|
||||||
|
|
||||||
|
**请描述您的问题.**
|
||||||
|
|||||||
31
.github/workflows/issue-comment.yml
vendored
31
.github/workflows/issue-comment.yml
vendored
@@ -21,44 +21,17 @@ jobs:
|
|||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '状态:待反馈'
|
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: '状态:待反馈'
|
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'
|
||||||
|
|||||||
48
.github/workflows/jms-build-test.yml
vendored
48
.github/workflows/jms-build-test.yml
vendored
@@ -1,55 +1,35 @@
|
|||||||
name: "Run Build Test"
|
name: "Run Build Test"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
branches:
|
||||||
- 'Dockerfile'
|
- pr@*
|
||||||
- 'Dockerfile-*'
|
- repr@*
|
||||||
- 'pyproject.toml'
|
|
||||||
- 'poetry.lock'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: docker/setup-qemu-action@v3
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Check Dockerfile
|
- uses: docker/setup-qemu-action@v2
|
||||||
run: |
|
|
||||||
test -f Dockerfile-ce || cp -f Dockerfile Dockerfile-ce
|
|
||||||
|
|
||||||
- name: Build CE Image
|
- uses: docker/setup-buildx-action@v2
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
|
- uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
file: Dockerfile-ce
|
tags: jumpserver/core:test
|
||||||
tags: jumpserver/core-ce:test
|
file: Dockerfile
|
||||||
platforms: linux/amd64
|
|
||||||
build-args: |
|
build-args: |
|
||||||
APT_MIRROR=http://deb.debian.org
|
APT_MIRROR=http://deb.debian.org
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
PIP_MIRROR=https://pypi.org/simple
|
||||||
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
|
||||||
|
|
||||||
- name: Prepare EE Image
|
- uses: LouisBrunner/checks-action@v1.5.0
|
||||||
run: |
|
if: always()
|
||||||
sed -i 's@^FROM registry.fit2cloud.com@# FROM registry.fit2cloud.com@g' Dockerfile-ee
|
|
||||||
sed -i 's@^COPY --from=build-xpack@# COPY --from=build-xpack@g' Dockerfile-ee
|
|
||||||
|
|
||||||
- name: Build EE Image
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
with:
|
||||||
context: .
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
push: false
|
name: Check Build
|
||||||
file: Dockerfile-ee
|
conclusion: ${{ job.status }}
|
||||||
tags: jumpserver/core-ee:test
|
|
||||||
platforms: linux/amd64
|
|
||||||
build-args: |
|
|
||||||
APT_MIRROR=http://deb.debian.org
|
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
|
||||||
PIP_JMS_MIRROR=https://pypi.org/simple
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|||||||
2
.github/workflows/sync-gitee.yml
vendored
2
.github/workflows/sync-gitee.yml
vendored
@@ -20,4 +20,4 @@ jobs:
|
|||||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
|
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
|
||||||
with:
|
with:
|
||||||
source-repo: 'git@github.com:jumpserver/jumpserver.git'
|
source-repo: 'git@github.com:jumpserver/jumpserver.git'
|
||||||
destination-repo: 'git@gitee.com:fit2cloud-feizhiyun/JumpServer.git'
|
destination-repo: 'git@gitee.com:jumpserver/jumpserver.git'
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,6 +35,7 @@ celerybeat-schedule.db
|
|||||||
docs/_build/
|
docs/_build/
|
||||||
xpack
|
xpack
|
||||||
xpack.bak
|
xpack.bak
|
||||||
|
logs/*
|
||||||
### Vagrant ###
|
### Vagrant ###
|
||||||
.vagrant/
|
.vagrant/
|
||||||
release/*
|
release/*
|
||||||
@@ -42,5 +43,3 @@ releashe
|
|||||||
/apps/script.py
|
/apps/script.py
|
||||||
data/*
|
data/*
|
||||||
test.py
|
test.py
|
||||||
.history/
|
|
||||||
.test/
|
|
||||||
|
|||||||
104
Dockerfile
Normal file
104
Dockerfile
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
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 \
|
||||||
|
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"
|
||||||
|
|
||||||
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
||||||
|
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||||
|
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
||||||
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
|
&& mkdir -p /root/.ssh/ \
|
||||||
|
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /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/*
|
||||||
|
|
||||||
|
ARG DOWNLOAD_URL=https://download.jumpserver.org
|
||||||
|
|
||||||
|
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
|
||||||
|
VOLUME /opt/jumpserver/data
|
||||||
|
VOLUME /opt/jumpserver/logs
|
||||||
|
|
||||||
|
ENV LANG=zh_CN.UTF-8
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
136
Dockerfile-ce
136
Dockerfile-ce
@@ -1,136 +0,0 @@
|
|||||||
FROM python:3.11-slim-bullseye as stage-1
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ENV VERSION=$VERSION
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
ADD . .
|
|
||||||
RUN echo > /opt/jumpserver/config.yml \
|
|
||||||
&& cd utils && bash -ixeu build.sh
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye as stage-2
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG BUILD_DEPENDENCIES=" \
|
|
||||||
g++ \
|
|
||||||
make \
|
|
||||||
pkg-config"
|
|
||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
|
||||||
freetds-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libkrb5-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxmlsec1-dev \
|
|
||||||
libxmlsec1-openssl \
|
|
||||||
freerdp2-dev \
|
|
||||||
libaio-dev"
|
|
||||||
|
|
||||||
ARG TOOLS=" \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
default-libmysqlclient-dev \
|
|
||||||
default-mysql-client \
|
|
||||||
git \
|
|
||||||
git-lfs \
|
|
||||||
unzip \
|
|
||||||
xz-utils \
|
|
||||||
wget"
|
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
|
||||||
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
|
||||||
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
|
||||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
|
||||||
&& echo "no" | dpkg-reconfigure dash
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
|
|
||||||
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
|
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
|
||||||
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
|
|
||||||
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
|
|
||||||
set -ex \
|
|
||||||
&& python3 -m venv /opt/py3 \
|
|
||||||
&& pip install poetry -i ${PIP_MIRROR} \
|
|
||||||
&& poetry config virtualenvs.create false \
|
|
||||||
&& . /opt/py3/bin/activate \
|
|
||||||
&& poetry install
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye
|
|
||||||
ARG TARGETARCH
|
|
||||||
ENV LANG=zh_CN.UTF-8 \
|
|
||||||
PATH=/opt/py3/bin:$PATH
|
|
||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
|
||||||
libjpeg-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libx11-dev \
|
|
||||||
freerdp2-dev \
|
|
||||||
libxmlsec1-openssl"
|
|
||||||
|
|
||||||
ARG TOOLS=" \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
default-libmysqlclient-dev \
|
|
||||||
default-mysql-client \
|
|
||||||
iputils-ping \
|
|
||||||
locales \
|
|
||||||
netcat-openbsd \
|
|
||||||
nmap \
|
|
||||||
openssh-client \
|
|
||||||
patch \
|
|
||||||
sshpass \
|
|
||||||
telnet \
|
|
||||||
vim \
|
|
||||||
wget"
|
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
|
||||||
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
|
||||||
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
|
||||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
|
||||||
&& mkdir -p /root/.ssh/ \
|
|
||||||
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
|
||||||
&& echo "no" | dpkg-reconfigure dash \
|
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
|
||||||
&& sed -i "s@# export @export @g" ~/.bashrc \
|
|
||||||
&& sed -i "s@# alias @alias @g" ~/.bashrc
|
|
||||||
|
|
||||||
ARG RECEPTOR_VERSION=v1.4.5
|
|
||||||
RUN set -ex \
|
|
||||||
&& wget -O /opt/receptor.tar.gz https://github.com/ansible/receptor/releases/download/${RECEPTOR_VERSION}/receptor_${RECEPTOR_VERSION/v/}_linux_${TARGETARCH}.tar.gz \
|
|
||||||
&& tar -xf /opt/receptor.tar.gz -C /usr/local/bin/ \
|
|
||||||
&& chown root:root /usr/local/bin/receptor \
|
|
||||||
&& chmod 755 /usr/local/bin/receptor \
|
|
||||||
&& rm -f /opt/receptor.tar.gz
|
|
||||||
|
|
||||||
COPY --from=stage-2 /opt/py3 /opt/py3
|
|
||||||
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
|
|
||||||
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ENV VERSION=$VERSION
|
|
||||||
|
|
||||||
VOLUME /opt/jumpserver/data
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
||||||
@@ -1,5 +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 registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
|
FROM jumpserver/core:${VERSION}
|
||||||
|
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
||||||
|
|
||||||
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
set -ex \
|
||||||
|
&& pip install -r requirements/requirements_xpack.txt
|
||||||
|
|||||||
96
Dockerfile.loong64
Normal file
96
Dockerfile.loong64
Normal 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" > /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 $(grep 'PyNaCl' requirements/requirements.txt) \
|
||||||
|
&& GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \
|
||||||
|
&& 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"]
|
||||||
70
README.md
70
README.md
@@ -10,25 +10,10 @@
|
|||||||
<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>
|
<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>
|
||||||
|
|
||||||
|
--------------------------
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
|
||||||
</p>
|
|
||||||
|
|
||||||
------------------------------
|
|
||||||
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
|
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 连接各类应用。
|
|
||||||
|
|
||||||
## 产品特色
|
## 产品特色
|
||||||
|
|
||||||
- **开源**: 零门槛,线上快速获取和安装;
|
- **开源**: 零门槛,线上快速获取和安装;
|
||||||
@@ -37,10 +22,12 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
|
|||||||
- **多云支持**: 一套系统,同时管理不同云上面的资产;
|
- **多云支持**: 一套系统,同时管理不同云上面的资产;
|
||||||
- **多租户**: 一套系统,多个子公司或部门同时使用;
|
- **多租户**: 一套系统,多个子公司或部门同时使用;
|
||||||
- **云端存储**: 审计录像云端存储,永不丢失;
|
- **云端存储**: 审计录像云端存储,永不丢失;
|
||||||
|
- **多应用支持**: 全面支持各类资产,包括服务器、数据库、Windows RemoteApp、Kubernetes 等;
|
||||||
|
- **安全可靠**: 被广泛使用、验证和信赖,连续 9 年的持续研发投入和产品更新升级。
|
||||||
|
|
||||||
## UI 展示
|
## UI 展示
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
|
|
||||||
@@ -54,14 +41,13 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
|
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
|
||||||
|
- [手动安装](https://github.com/jumpserver/installer)
|
||||||
- [产品文档](https://docs.jumpserver.org)
|
- [产品文档](https://docs.jumpserver.org)
|
||||||
- [在线学习](https://edu.fit2cloud.com/page/2635362)
|
|
||||||
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
|
- [知识库](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构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
|
||||||
- [万华化学:通过JumpServer管理全球化分布式IT资产,并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
|
- [万华化学:通过JumpServer管理全球化分布式IT资产,并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
|
||||||
- [雪花啤酒:JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
|
- [雪花啤酒:JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
|
||||||
@@ -75,33 +61,32 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
|
|||||||
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
|
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
|
||||||
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
|
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
|
||||||
|
|
||||||
## 社区交流
|
## 社区
|
||||||
|
|
||||||
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
|
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)
|
||||||
|
或加入到我们的社区当中进行进一步交流沟通。
|
||||||
|
|
||||||
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
|
### 微信交流群
|
||||||
|
|
||||||
|
<img src="https://download.jumpserver.org/images/wecom-group.jpeg" alt="微信群二维码" width="200"/>
|
||||||
|
|
||||||
### 参与贡献
|
### 参与贡献
|
||||||
|
|
||||||
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
|
欢迎提交 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 项目 |
|
| [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 项目 |
|
| [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 项目 |
|
| [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/) |
|
| [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 项目 |
|
| [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 项目 |
|
||||||
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
|
| [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 客户端 项目 |
|
||||||
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
|
| [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 安装包 项目 |
|
||||||
| [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 安装包 项目 |
|
|
||||||
|
|
||||||
## 安全说明
|
## 安全说明
|
||||||
|
|
||||||
@@ -111,9 +96,14 @@ JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.ju
|
|||||||
- 邮箱:support@fit2cloud.com
|
- 邮箱:support@fit2cloud.com
|
||||||
- 电话:400-052-0755
|
- 电话:400-052-0755
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
- [Apache Guacamole](https://guacamole.apache.org/): Web 页面连接 RDP、SSH、VNC 等协议资产,JumpServer Lion 组件使用到该项目;
|
||||||
|
- [OmniDB](https://omnidb.org/): Web 页面连接使用数据库,JumpServer Web 数据库组件使用到该项目。
|
||||||
|
|
||||||
## License & Copyright
|
## License & Copyright
|
||||||
|
|
||||||
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved.
|
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
|
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
|
compliance with the License. You may obtain a copy of the License at
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ If you find a security problem, please contact us directly:
|
|||||||
- 400-052-0755
|
- 400-052-0755
|
||||||
|
|
||||||
### License & Copyright
|
### License & Copyright
|
||||||
Copyright (c) 2014-2024 FIT2CLOUD Tech, Inc., All rights reserved.
|
Copyright (c) 2014-2022 FIT2CLOUD Tech, Inc., All rights reserved.
|
||||||
|
|
||||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -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 *
|
|
||||||
|
|||||||
@@ -1,40 +1,35 @@
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.generics import ListAPIView, CreateAPIView
|
from rest_framework.generics import ListAPIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
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.permissions import UserConfirmation, ConfirmType
|
||||||
from common.api.mixin import ExtraFilterFieldsMixin
|
from common.views.mixins import RecordViewLogMixin
|
||||||
from common.permissions import IsValidUser
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountViewSet', 'AccountSecretsViewSet',
|
'AccountViewSet', 'AccountSecretsViewSet',
|
||||||
'AccountHistoriesSecretAPI', 'AssetAccountBulkCreateApi',
|
'AccountHistoriesSecretAPI'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
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',
|
'username_suggestions': 'accounts.view_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):
|
||||||
@@ -48,29 +43,25 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
accounts = asset.accounts.all()
|
accounts = asset.accounts.all()
|
||||||
else:
|
else:
|
||||||
accounts = Account.objects.none()
|
accounts = []
|
||||||
accounts = self.filter_queryset(accounts)
|
accounts = self.filter_queryset(accounts)
|
||||||
serializer = serializers.AccountSerializer(accounts, many=True)
|
serializer = serializers.AccountSerializer(accounts, many=True)
|
||||||
return Response(data=serializer.data)
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
@action(
|
@action(methods=['get'], detail=False, url_path='username-suggestions')
|
||||||
methods=['post'], detail=False, url_path='username-suggestions',
|
|
||||||
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])
|
||||||
@@ -80,14 +71,8 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
usernames = common + others
|
usernames = common + others
|
||||||
return Response(data=usernames)
|
return Response(data=usernames)
|
||||||
|
|
||||||
@action(methods=['patch'], detail=False, url_path='clear-secret')
|
|
||||||
def clear_secret(self, request, *args, **kwargs):
|
|
||||||
account_ids = request.data.get('account_ids', [])
|
|
||||||
self.model.objects.filter(id__in=account_ids).update(secret=None)
|
|
||||||
return Response(status=HTTP_200_OK)
|
|
||||||
|
|
||||||
|
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
||||||
class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
|
|
||||||
"""
|
"""
|
||||||
因为可能要导出所有账号,所以单独建立了一个 viewset
|
因为可能要导出所有账号,所以单独建立了一个 viewset
|
||||||
"""
|
"""
|
||||||
@@ -102,21 +87,7 @@ class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AssetAccountBulkCreateApi(CreateAPIView):
|
class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
|
||||||
serializer_class = serializers.AssetAccountBulkSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'POST': 'accounts.add_account',
|
|
||||||
}
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.create(serializer.validated_data)
|
|
||||||
serializer = serializers.AssetAccountBulkSerializerResultSerializer(data, many=True)
|
|
||||||
return Response(data=serializer.data, status=HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, 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']
|
||||||
@@ -128,19 +99,14 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixi
|
|||||||
def get_object(self):
|
def get_object(self):
|
||||||
return get_object_or_404(Account, pk=self.kwargs.get('pk'))
|
return get_object_or_404(Account, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filter_spm_queryset(resource_ids, queryset):
|
|
||||||
return queryset.filter(history_id__in=resource_ids)
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -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,37 @@ __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)
|
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)
|
||||||
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_info['ansible_enabled'] or \
|
||||||
|
not asset.auto_info['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
|
||||||
|
|||||||
@@ -1,71 +1,22 @@
|
|||||||
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.mixins import AccountRecordViewLogMixin
|
|
||||||
from accounts.models import AccountTemplate
|
|
||||||
from accounts.tasks import template_sync_related_accounts
|
|
||||||
from assets.const import Protocol
|
|
||||||
from authentication.permissions import UserConfirmation, ConfirmType
|
|
||||||
from common.drf.filters import BaseFilterSet
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
from common.permissions import UserConfirmation, ConfirmType
|
||||||
|
|
||||||
|
from common.views.mixins import RecordViewLogMixin
|
||||||
class AccountTemplateFilterSet(BaseFilterSet):
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
protocols = drf_filters.CharFilter(method='filter_protocols')
|
from accounts import serializers
|
||||||
|
from accounts.models import AccountTemplate
|
||||||
class Meta:
|
|
||||||
model = AccountTemplate
|
|
||||||
fields = ('username', 'name')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filter_protocols(queryset, name, value):
|
|
||||||
secret_types = set()
|
|
||||||
protocols = value.split(',')
|
|
||||||
protocol_secret_type_map = Protocol.settings()
|
|
||||||
for p in protocols:
|
|
||||||
if p not in protocol_secret_type_map:
|
|
||||||
continue
|
|
||||||
_st = protocol_secret_type_map[p].get('secret_types', [])
|
|
||||||
secret_types.update(_st)
|
|
||||||
if not secret_types:
|
|
||||||
secret_types = ['password']
|
|
||||||
queryset = queryset.filter(secret_type__in=secret_types)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
||||||
model = AccountTemplate
|
model = AccountTemplate
|
||||||
filterset_class = AccountTemplateFilterSet
|
filterset_fields = ("username", 'name')
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 filter_queryset(self, queryset):
|
||||||
def execute(self, request, *args, **kwargs):
|
queryset = super().filter_queryset(queryset)
|
||||||
record_ids = request.data.get('record_ids')
|
eid = self.request.query_params.get('execution_id')
|
||||||
records = self.get_queryset().filter(id__in=record_ids)
|
execution = get_object_or_none(AutomationExecution, pk=eid)
|
||||||
execution_count = records.values_list('execution_id', flat=True).distinct().count()
|
if execution:
|
||||||
if execution_count != 1:
|
queryset = queryset.filter(execution=execution)
|
||||||
return Response(
|
return queryset
|
||||||
{'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 secret(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
serializer = self.get_serializer(instance)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
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.const import Source
|
||||||
from accounts.filters import GatheredAccountFilterSet
|
from accounts.filters import GatheredAccountFilterSet
|
||||||
from accounts.models import GatherAccountsAutomation
|
from accounts.models import GatherAccountsAutomation
|
||||||
from accounts.models import GatheredAccount
|
from accounts.models import GatheredAccount
|
||||||
@@ -20,8 +22,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
|
||||||
|
|
||||||
|
|
||||||
@@ -48,12 +50,22 @@ class GatheredAccountViewSet(OrgBulkModelViewSet):
|
|||||||
'default': serializers.GatheredAccountSerializer,
|
'default': serializers.GatheredAccountSerializer,
|
||||||
}
|
}
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'sync_accounts': 'assets.add_gatheredaccount',
|
'sync_account': 'assets.add_gatheredaccount',
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, url_path='sync-accounts')
|
@action(methods=['post'], detail=True, url_path='sync')
|
||||||
def sync_accounts(self, request, *args, **kwargs):
|
def sync_account(self, request, *args, **kwargs):
|
||||||
gathered_account_ids = request.data.get('gathered_account_ids')
|
gathered_account = super().get_object()
|
||||||
gathered_accounts = self.model.objects.filter(id__in=gathered_account_ids)
|
asset = gathered_account.asset
|
||||||
self.model.sync_accounts(gathered_accounts)
|
username = gathered_account.username
|
||||||
|
accounts = asset.accounts.filter(username=username)
|
||||||
|
|
||||||
|
if accounts.exists():
|
||||||
|
accounts.update(source=Source.COLLECTED)
|
||||||
|
else:
|
||||||
|
asset.accounts.model.objects.create(
|
||||||
|
asset=asset, username=username,
|
||||||
|
name=f'{username}-{_("Collected")}',
|
||||||
|
source=Source.COLLECTED
|
||||||
|
)
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
return Response(status=status.HTTP_201_CREATED)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ class AccountsConfig(AppConfig):
|
|||||||
name = 'accounts'
|
name = '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
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
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.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')
|
||||||
|
|
||||||
|
|
||||||
class RecipientsNotFound(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountHandler:
|
class BaseAccountHandler:
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_data(cls, serializer_data, data=None):
|
def unpack_data(cls, serializer_data, data=None):
|
||||||
@@ -74,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():
|
||||||
@@ -113,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))
|
||||||
|
|
||||||
print('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
|
logger.info('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
|
||||||
return data_map
|
return data_map
|
||||||
|
|
||||||
|
|
||||||
@@ -126,8 +109,8 @@ 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):
|
||||||
print(
|
logger.info(
|
||||||
'\n'
|
'\n'
|
||||||
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
|
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
|
||||||
''
|
''
|
||||||
@@ -136,7 +119,7 @@ class AccountBackupHandler:
|
|||||||
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
|
||||||
|
|
||||||
@@ -144,23 +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)
|
||||||
print('创建备份文件完成: 用时 {}s'.format(timedelta))
|
logger.info('步骤完成: 用时 {}s'.format(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'
|
||||||
'\033[32m>>> 开始发送备份邮件\033[0m'
|
'\033[32m>>> 发送备份邮件\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
plan_name = self.plan_name
|
plan_name = self.plan_name
|
||||||
@@ -168,34 +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)
|
||||||
print('邮件已发送至{}({})'.format(user, user.email))
|
logger.info('邮件已发送至{}({})'.format(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
|
|
||||||
for rec in recipients:
|
|
||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
|
||||||
if password:
|
|
||||||
print('\033[32m>>> 使用加密密码对文件进行加密中\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)
|
|
||||||
print('备份文件将发送至{}({})'.format(rec.name, rec.id))
|
|
||||||
for file in files:
|
for file in files:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
@@ -203,29 +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()
|
||||||
print('\n已完成对任务状态的更新\n')
|
logger.info('已完成对任务状态的更新')
|
||||||
|
|
||||||
@staticmethod
|
def step_finished(self, is_success):
|
||||||
def step_finished(is_success):
|
|
||||||
if is_success:
|
if is_success:
|
||||||
print('任务执行成功')
|
logger.info('任务执行成功')
|
||||||
else:
|
else:
|
||||||
print('任务执行失败')
|
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('任务执行被异常中断')
|
logger.error('任务执行被异常中断')
|
||||||
print('下面打印发生异常的 Traceback 信息 : ')
|
logger.info('下面打印发生异常的 Traceback 信息 : ')
|
||||||
print(e)
|
logger.error(e, exc_info=True)
|
||||||
error = str(e)
|
error = str(e)
|
||||||
else:
|
else:
|
||||||
is_success = True
|
is_success = True
|
||||||
@@ -234,62 +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', [])
|
|
||||||
if not obj_recipients_part_one and not obj_recipients_part_two:
|
|
||||||
print(
|
|
||||||
'\n'
|
|
||||||
'\033[31m>>> 该备份任务未分配sftp服务器\033[0m'
|
|
||||||
''
|
|
||||||
)
|
|
||||||
raise RecipientsNotFound('Not Found Recipients')
|
|
||||||
if obj_recipients_part_one and obj_recipients_part_two:
|
|
||||||
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\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):
|
|
||||||
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'
|
|
||||||
'\033[31m>>> 该备份任务未分配收件人\033[0m'
|
|
||||||
''
|
|
||||||
)
|
|
||||||
raise RecipientsNotFound('Not Found Recipients')
|
|
||||||
if recipients_part_one and recipients_part_two:
|
|
||||||
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\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):
|
||||||
print('任务开始: {}'.format(local_now_display()))
|
logger.info('任务开始: {}'.format(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('任务运行出现异常')
|
logger.error('任务运行出现异常')
|
||||||
print('下面显示异常 Traceback 信息: ')
|
logger.error('下面显示异常 Traceback 信息: ')
|
||||||
print(e)
|
logger.error(e, exc_info=True)
|
||||||
finally:
|
finally:
|
||||||
print('\n任务结束: {}'.format(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(timedelta))
|
logger.info('用时: {}'.format(timedelta))
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import time
|
|||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
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):
|
||||||
@@ -19,7 +23,7 @@ class AccountBackupManager:
|
|||||||
|
|
||||||
def do_run(self):
|
def do_run(self):
|
||||||
execution = self.execution
|
execution = self.execution
|
||||||
print('\n\033[33m# 账号备份计划正在执行\033[0m')
|
logger.info('\n\033[33m# 账号备份计划正在执行\033[0m')
|
||||||
handler = AccountBackupHandler(execution)
|
handler = AccountBackupHandler(execution)
|
||||||
handler.run()
|
handler.run()
|
||||||
|
|
||||||
@@ -31,10 +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)
|
||||||
print('计划执行结束 {}\n'.format(local_now_display()))
|
logger.info('计划执行结束 {}\n'.format(local_now_display()))
|
||||||
self.timedelta = self.time_end - self.time_start
|
self.timedelta = self.time_end - self.time_start
|
||||||
print('用时: {}s'.format(self.timedelta))
|
logger.info('用时: {}s'.format(self.timedelta))
|
||||||
self.execution.timedelta = self.timedelta
|
self.execution.timedelta = self.timedelta
|
||||||
self.execution.save()
|
self.execution.save()
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
- hosts: custom
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
ansible_become: false
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test privileged account (paramiko)
|
|
||||||
ssh_ping:
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
|
||||||
become: "{{ custom_become | default(False) }}"
|
|
||||||
become_method: "{{ custom_become_method | default('su') }}"
|
|
||||||
become_user: "{{ custom_become_user | default('') }}"
|
|
||||||
become_password: "{{ custom_become_password | default('') }}"
|
|
||||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
|
||||||
register: ping_info
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Change asset password (paramiko)
|
|
||||||
custom_command:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
|
||||||
become: "{{ custom_become | default(False) }}"
|
|
||||||
become_method: "{{ custom_become_method | default('su') }}"
|
|
||||||
become_user: "{{ custom_become_user | default('') }}"
|
|
||||||
become_password: "{{ custom_become_password | default('') }}"
|
|
||||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
commands: "{{ params.commands }}"
|
|
||||||
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
|
|
||||||
ignore_errors: true
|
|
||||||
when: ping_info is succeeded
|
|
||||||
register: change_info
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Verify password (paramiko)
|
|
||||||
ssh_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
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) }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
|
||||||
delegate_to: localhost
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
id: change_secret_by_ssh
|
|
||||||
name: "{{ 'SSH account change secret' | trans }}"
|
|
||||||
category:
|
|
||||||
- device
|
|
||||||
- host
|
|
||||||
type:
|
|
||||||
- all
|
|
||||||
method: change_secret
|
|
||||||
protocol: ssh
|
|
||||||
priority: 50
|
|
||||||
params:
|
|
||||||
- name: commands
|
|
||||||
type: list
|
|
||||||
label: "{{ 'Params commands label' | trans }}"
|
|
||||||
default: [ '' ]
|
|
||||||
help_text: "{{ 'Params commands help text' | trans }}"
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
SSH account change secret:
|
|
||||||
zh: '使用 SSH 命令行自定义改密'
|
|
||||||
ja: 'SSH コマンドライン方式でカスタムパスワード変更'
|
|
||||||
en: 'Custom password change by SSH command line'
|
|
||||||
|
|
||||||
Params commands help text:
|
|
||||||
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 {username}、{password}、{login_password}格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br />4. username {username} privilege 0 password {password} <br />5. end'
|
|
||||||
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />{ユーザー名}、{パスワード}、{login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.{login_password}<br />3 .ターミナルの設定<br / >4. ユーザー名 {ユーザー名} 権限 0 パスワード {パスワード} <br />5. 終了'
|
|
||||||
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use {username}, {password}, {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. {login_password}<br />3. configure terminal<br / >4. username {username} privilege 0 password {password} <br />5. end'
|
|
||||||
|
|
||||||
Params commands label:
|
|
||||||
zh: '自定义命令'
|
|
||||||
ja: 'カスタムコマンド'
|
|
||||||
en: 'Custom command'
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
id: change_secret_mongodb
|
id: change_secret_mongodb
|
||||||
name: "{{ 'MongoDB account change secret' | trans }}"
|
name: Change secret for MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account change secret:
|
|
||||||
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号改密
|
|
||||||
ja: Ansible mongodb モジュールを使用して MongoDB アカウントのパスワード変更
|
|
||||||
en: Using Ansible module mongodb to change MongoDB account secret
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,13 +1,7 @@
|
|||||||
id: change_secret_mysql
|
id: change_secret_mysql
|
||||||
name: "{{ 'MySQL account change secret' | trans }}"
|
name: Change secret for MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
- mariadb
|
- mariadb
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account change secret:
|
|
||||||
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号改密
|
|
||||||
ja: Ansible mysql モジュールを使用して MySQL アカウントのパスワード変更
|
|
||||||
en: Using Ansible module mysql to change MySQL account secret
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_oracle
|
id: change_secret_oracle
|
||||||
name: "{{ 'Oracle account change secret' | trans }}"
|
name: Change secret for Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account change secret:
|
|
||||||
zh: Oracle 账号改密
|
|
||||||
ja: Oracle アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_postgresql
|
id: change_secret_postgresql
|
||||||
name: "{{ 'PostgreSQL account change secret' | trans }}"
|
name: Change secret for PostgreSQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- postgresql
|
- postgresql
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account change secret:
|
|
||||||
zh: PostgreSQL 账号改密
|
|
||||||
ja: PostgreSQL アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_sqlserver
|
id: change_secret_sqlserver
|
||||||
name: "{{ 'SQLServer account change secret' | trans }}"
|
name: Change secret for SQLServer
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- sqlserver
|
- sqlserver
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
SQLServer account change secret:
|
|
||||||
zh: SQLServer 账号改密
|
|
||||||
ja: SQLServer アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -1,101 +1,54 @@
|
|||||||
- 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: 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: 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: "{{ kwargs.dest }}"
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
regexp: "{{ kwargs.regexp }}"
|
||||||
state: absent
|
state: absent
|
||||||
when:
|
when:
|
||||||
- account.secret_type == "ssh_key"
|
- secret_type == "ssh_key"
|
||||||
- ssh_params.strategy == "set_jms"
|
- kwargs.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: "{{ kwargs.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: 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) }}"
|
when: secret_type == "password"
|
||||||
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"
|
|
||||||
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: secret_type == "ssh_key"
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|||||||
@@ -1,61 +1,6 @@
|
|||||||
id: change_secret_aix
|
id: change_secret_aix
|
||||||
name: "{{ 'AIX account change secret' | trans }}"
|
name: Change secret for aix
|
||||||
category: host
|
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:
|
|
||||||
AIX account change secret:
|
|
||||||
zh: '使用 Ansible 模块 user 执行账号改密 (DES)'
|
|
||||||
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
|
||||||
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'
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,101 +1,54 @@
|
|||||||
- 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: 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: 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: "{{ kwargs.dest }}"
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
regexp: "{{ kwargs.regexp }}"
|
||||||
state: absent
|
state: absent
|
||||||
when:
|
when:
|
||||||
- account.secret_type == "ssh_key"
|
- secret_type == "ssh_key"
|
||||||
- ssh_params.strategy == "set_jms"
|
- kwargs.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: "{{ kwargs.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: 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) }}"
|
when: secret_type == "password"
|
||||||
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"
|
|
||||||
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: secret_type == "ssh_key"
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|||||||
@@ -1,63 +1,7 @@
|
|||||||
id: change_secret_posix
|
id: change_secret_posix
|
||||||
name: "{{ 'Posix account change secret' | trans }}"
|
name: Change secret for posix
|
||||||
category: host
|
category: host
|
||||||
type:
|
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:
|
|
||||||
Posix account change secret:
|
|
||||||
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)'
|
|
||||||
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
|
||||||
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'
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,26 +1,7 @@
|
|||||||
id: change_secret_local_windows
|
id: change_secret_local_windows
|
||||||
name: "{{ 'Windows account change secret' | trans }}"
|
name: Change secret local account for Windows
|
||||||
version: 1
|
version: 1
|
||||||
method: change_secret
|
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:
|
|
||||||
Windows account change secret:
|
|
||||||
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密'
|
|
||||||
ja: 'Ansible win_user モジュールを使用して 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)'
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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)'
|
|
||||||
|
|
||||||
@@ -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
|
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, lazyproperty
|
||||||
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,63 +27,45 @@ 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['secret_type']
|
||||||
self.secret_strategy = self.execution.snapshot.get(
|
self.secret_strategy = self.execution.snapshot.get(
|
||||||
'secret_strategy', SecretStrategy.custom
|
'secret_strategy', SecretStrategy.custom
|
||||||
)
|
)
|
||||||
self.ssh_key_change_strategy = self.execution.snapshot.get(
|
self.ssh_key_change_strategy = self.execution.snapshot.get(
|
||||||
'ssh_key_change_strategy', SSHKeyStrategy.add
|
'ssh_key_change_strategy', SSHKeyStrategy.add
|
||||||
)
|
)
|
||||||
self.account_ids = self.execution.snapshot['accounts']
|
self.snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
return AutomationTypes.change_secret
|
return AutomationTypes.change_secret
|
||||||
|
|
||||||
def get_ssh_params(self, account, secret, secret_type):
|
def get_kwargs(self, account, secret):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if secret_type != SecretType.SSH_KEY:
|
if self.secret_type != SecretType.SSH_KEY:
|
||||||
return kwargs
|
return kwargs
|
||||||
kwargs['strategy'] = self.ssh_key_change_strategy
|
kwargs['strategy'] = self.ssh_key_change_strategy
|
||||||
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
|
||||||
|
|
||||||
def secret_generator(self, secret_type):
|
@lazyproperty
|
||||||
|
def secret_generator(self):
|
||||||
return SecretGenerator(
|
return SecretGenerator(
|
||||||
self.secret_strategy, secret_type,
|
self.secret_strategy, self.secret_type,
|
||||||
self.execution.snapshot.get('password_rules')
|
self.execution.snapshot.get('password_rules')
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_secret(self, secret_type):
|
def get_secret(self):
|
||||||
if self.secret_strategy == SecretStrategy.custom:
|
if self.secret_strategy == SecretStrategy.custom:
|
||||||
return self.execution.snapshot['secret']
|
return self.execution.snapshot['secret']
|
||||||
else:
|
else:
|
||||||
return self.secret_generator(secret_type).get_secret()
|
return self.secret_generator.get_secret()
|
||||||
|
|
||||||
def get_accounts(self, privilege_account):
|
|
||||||
if not privilege_account:
|
|
||||||
print(f'not privilege account')
|
|
||||||
return []
|
|
||||||
|
|
||||||
asset = privilege_account.asset
|
|
||||||
accounts = asset.accounts.all()
|
|
||||||
accounts = accounts.filter(id__in=self.account_ids)
|
|
||||||
if self.secret_type:
|
|
||||||
accounts = accounts.filter(secret_type=self.secret_type)
|
|
||||||
|
|
||||||
if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED:
|
|
||||||
accounts = accounts.filter(privileged=False).exclude(
|
|
||||||
username__in=['root', 'administrator', privilege_account.username]
|
|
||||||
)
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
def host_callback(
|
def host_callback(
|
||||||
self, host, asset=None, account=None,
|
self, host, asset=None, account=None,
|
||||||
@@ -96,67 +78,61 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
if host.get('error'):
|
if host.get('error'):
|
||||||
return host
|
return host
|
||||||
|
|
||||||
accounts = self.get_accounts(account)
|
accounts = asset.accounts.all()
|
||||||
|
if account:
|
||||||
|
accounts = accounts.exclude(username=account.username)
|
||||||
|
|
||||||
|
if '*' not in self.snapshot_account_usernames:
|
||||||
|
accounts = accounts.filter(username__in=self.snapshot_account_usernames)
|
||||||
|
|
||||||
|
accounts = accounts.filter(secret_type=self.secret_type)
|
||||||
if not accounts:
|
if not accounts:
|
||||||
print('没有发现待处理的账号: %s 用户ID: %s 类型: %s' % (
|
print('没有发现待改密账号: %s 用户名: %s 类型: %s' % (
|
||||||
asset.name, self.account_ids, self.secret_type
|
asset.name, self.snapshot_account_usernames, 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 = []
|
||||||
|
host['secret_type'] = self.secret_type
|
||||||
|
|
||||||
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 \n')
|
||||||
return inventory_hosts
|
return inventory_hosts
|
||||||
|
|
||||||
host['ssh_params'] = {}
|
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
h = deepcopy(host)
|
h = deepcopy(host)
|
||||||
secret_type = account.secret_type
|
|
||||||
h['name'] += '(' + account.username + ')'
|
h['name'] += '(' + account.username + ')'
|
||||||
if self.secret_type is None:
|
new_secret = self.get_secret()
|
||||||
new_secret = account.secret
|
|
||||||
else:
|
|
||||||
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(
|
|
||||||
asset=asset, account=account, execution=self.execution,
|
|
||||||
old_secret=account.secret, new_secret=new_secret,
|
|
||||||
)
|
|
||||||
records.append(recorder)
|
|
||||||
else:
|
|
||||||
record_id = self.record_map[asset_account_id]
|
|
||||||
try:
|
|
||||||
recorder = ChangeSecretRecord.objects.get(id=record_id)
|
|
||||||
except ChangeSecretRecord.DoesNotExist:
|
|
||||||
print(f"Record {record_id} not found")
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
recorder = ChangeSecretRecord(
|
||||||
|
asset=asset, account=account, execution=self.execution,
|
||||||
|
old_secret=account.secret, new_secret=new_secret,
|
||||||
|
)
|
||||||
|
records.append(recorder)
|
||||||
self.name_recorder_mapper[h['name']] = recorder
|
self.name_recorder_mapper[h['name']] = recorder
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
if secret_type == SecretType.SSH_KEY:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
||||||
new_secret = self.generate_public_key(new_secret)
|
new_secret = self.generate_public_key(new_secret)
|
||||||
|
|
||||||
h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type))
|
h['kwargs'] = self.get_kwargs(account, new_secret)
|
||||||
h['account'] = {
|
h['account'] = {
|
||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': secret_type,
|
'secret_type': account.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
|
||||||
|
|
||||||
@@ -164,46 +140,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 \
|
||||||
@@ -212,69 +169,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():
|
||||||
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
|
||||||
@@ -289,9 +212,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
|
||||||
|
|||||||
@@ -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 后期迁移到自动化策略中
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_accounts_mongodb
|
id: gather_accounts_mongodb
|
||||||
name: "{{ 'MongoDB account gather' | trans }}"
|
name: Gather account from MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account gather:
|
|
||||||
zh: MongoDB 账号收集
|
|
||||||
ja: MongoDB アカウントの収集
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: gather_accounts_mysql
|
id: gather_accounts_mysql
|
||||||
name: "{{ 'MySQL account gather' | trans }}"
|
name: Gather account from MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
- mariadb
|
- mariadb
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account gather:
|
|
||||||
zh: MySQL 账号收集
|
|
||||||
ja: MySQL アカウントの収集
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_accounts_oracle
|
id: gather_accounts_oracle
|
||||||
name: "{{ 'Oracle account gather' | trans }}"
|
name: Gather account from Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account gather:
|
|
||||||
zh: Oracle 账号收集
|
|
||||||
ja: Oracle アカウントの収集
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_accounts_postgresql
|
id: gather_accounts_postgresql
|
||||||
name: "{{ 'PostgreSQL account gather' | trans }}"
|
name: Gather account for PostgreSQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- postgresql
|
- postgresql
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account gather:
|
|
||||||
zh: PostgreSQL 账号收集
|
|
||||||
ja: PostgreSQL アカウントの収集
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -69,6 +60,4 @@ class GatherAccountsFilter:
|
|||||||
if not run_method_name:
|
if not run_method_name:
|
||||||
return info
|
return info
|
||||||
|
|
||||||
if hasattr(self, f'{run_method_name}_filter'):
|
return getattr(self, f'{run_method_name}_filter')(info)
|
||||||
return getattr(self, f'{run_method_name}_filter')(info)
|
|
||||||
return info
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
id: gather_accounts_posix
|
id: gather_accounts_posix
|
||||||
name: "{{ 'Posix account gather' | trans }}"
|
name: Gather posix account
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- linux
|
- linux
|
||||||
- unix
|
- unix
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
Posix account gather:
|
|
||||||
zh: 使用命令 getent passwd 收集 Posix 资产账号
|
|
||||||
ja: コマンド getent を使用してアセットアカウントを収集する
|
|
||||||
en: Using command getent to gather accounts
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
id: gather_accounts_windows
|
id: gather_accounts_windows
|
||||||
name: "{{ 'Windows account gather' | trans }}"
|
name: Gather account windows
|
||||||
version: 1
|
version: 1
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account gather:
|
|
||||||
zh: 使用命令 net user 收集 Windows 账号
|
|
||||||
ja: コマンド net user を使用して Windows アカウントを収集する
|
|
||||||
en: Using command net user to gather accounts
|
|
||||||
|
|||||||
@@ -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,10 +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')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
@@ -31,109 +22,29 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
self.host_asset_mapper[host['name']] = asset
|
self.host_asset_mapper[host['name']] = asset
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def filter_success_result(self, tp, result):
|
def filter_success_result(self, host, result):
|
||||||
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def generate_data(self, asset, result):
|
|
||||||
data = []
|
|
||||||
for username, info in result.items():
|
|
||||||
self.asset_username_mapper[str(asset.id)].add(username)
|
|
||||||
d = {'asset': asset, 'username': username, 'present': True}
|
|
||||||
if info.get('date'):
|
|
||||||
d['date_last_login'] = info['date']
|
|
||||||
if info.get('address'):
|
|
||||||
d['address_last_login'] = info['address'][:32]
|
|
||||||
data.append(d)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def collect_asset_account_info(self, asset, result):
|
|
||||||
data = self.generate_data(asset, result)
|
|
||||||
self.asset_account_info[asset] = data
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_nested_info(data, *keys):
|
def update_or_create_gathered_accounts(asset, result):
|
||||||
for key in keys:
|
with tmp_to_org(asset.org_id):
|
||||||
data = data.get(key, {})
|
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
||||||
if not data:
|
for username, data in result.items():
|
||||||
break
|
d = {'asset': asset, 'username': username, 'present': True}
|
||||||
return data
|
if data.get('date'):
|
||||||
|
d['date_last_login'] = data['date']
|
||||||
|
if data.get('address'):
|
||||||
|
d['address_last_login'] = data['address'][:32]
|
||||||
|
GatheredAccount.objects.update_or_create(
|
||||||
|
defaults=d, asset=asset, username=username,
|
||||||
|
)
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
info = self.get_nested_info(result, 'debug', 'res', 'info')
|
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||||
asset = self.host_asset_mapper.get(host)
|
asset = self.host_asset_mapper.get(host)
|
||||||
if asset and info:
|
if asset and info:
|
||||||
result = self.filter_success_result(asset.type, info)
|
result = self.filter_success_result(asset.type, info)
|
||||||
self.collect_asset_account_info(asset, result)
|
self.update_or_create_gathered_accounts(asset, result)
|
||||||
else:
|
else:
|
||||||
print(f'\033[31m Not found {host} info \033[0m\n')
|
logger.error("Not found info".format(host))
|
||||||
|
|
||||||
def update_or_create_accounts(self):
|
|
||||||
for asset, data in self.asset_account_info.items():
|
|
||||||
with tmp_to_org(asset.org_id):
|
|
||||||
gathered_accounts = []
|
|
||||||
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
|
||||||
for d in data:
|
|
||||||
username = d['username']
|
|
||||||
gathered_account, __ = GatheredAccount.objects.update_or_create(
|
|
||||||
defaults=d, asset=asset, username=username,
|
|
||||||
)
|
|
||||||
gathered_accounts.append(gathered_account)
|
|
||||||
if not self.is_sync_account:
|
|
||||||
continue
|
|
||||||
GatheredAccount.sync_accounts(gathered_accounts)
|
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
|
||||||
super().run(*args, **kwargs)
|
|
||||||
users, change_info = self.generate_send_users_and_change_info()
|
|
||||||
self.update_or_create_accounts()
|
|
||||||
self.send_email_if_need(users, change_info)
|
|
||||||
|
|
||||||
def generate_send_users_and_change_info(self):
|
|
||||||
recipients = self.execution.recipients
|
|
||||||
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()
|
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
from assets.automations.methods import get_platform_automation_methods
|
from assets.automations.methods import get_platform_automation_methods
|
||||||
|
|
||||||
|
|
||||||
|
def copy_change_secret_to_push_account(methods):
|
||||||
|
push_account = AutomationTypes.push_account
|
||||||
|
change_secret = AutomationTypes.change_secret
|
||||||
|
copy_methods = copy.deepcopy(methods)
|
||||||
|
for method in copy_methods:
|
||||||
|
if not method['id'].startswith(change_secret):
|
||||||
|
continue
|
||||||
|
copy_method = copy.deepcopy(method)
|
||||||
|
copy_method['method'] = push_account.value
|
||||||
|
copy_method['id'] = copy_method['id'].replace(
|
||||||
|
change_secret, push_account
|
||||||
|
)
|
||||||
|
copy_method['name'] = copy_method['name'].replace(
|
||||||
|
'Change secret', 'Push account'
|
||||||
|
)
|
||||||
|
methods.append(copy_method)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
platform_automation_methods = get_platform_automation_methods(BASE_DIR)
|
automation_methods = get_platform_automation_methods(BASE_DIR)
|
||||||
|
|
||||||
|
platform_automation_methods = copy_change_secret_to_push_account(automation_methods)
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
- hosts: mongodb
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test MongoDB connection
|
|
||||||
mongodb_ping:
|
|
||||||
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}}"
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: Display MongoDB version
|
|
||||||
debug:
|
|
||||||
var: db_info.server_version
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Change MongoDB password
|
|
||||||
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 }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
ignore_errors: true
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
mongodb_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ 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}}"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: push_account_mongodb
|
|
||||||
name: "{{ 'MongoDB account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- mongodb
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account push:
|
|
||||||
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号推送
|
|
||||||
ja: Ansible mongodb モジュールを使用してアカウントをプッシュする
|
|
||||||
en: Using Ansible module mongodb to push account
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
- hosts: mysql
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
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:
|
|
||||||
- name: Test MySQL connection
|
|
||||||
community.mysql.mysql_info:
|
|
||||||
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 }}"
|
|
||||||
filter: version
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: MySQL version
|
|
||||||
debug:
|
|
||||||
var: db_info.version.full
|
|
||||||
|
|
||||||
- name: Change MySQL password
|
|
||||||
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 }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
host: "%"
|
|
||||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
|
||||||
ignore_errors: true
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
community.mysql.mysql_info:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ 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 }}"
|
|
||||||
filter: version
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
id: push_account_mysql
|
|
||||||
name: "{{ 'MySQL account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- mysql
|
|
||||||
- mariadb
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account push:
|
|
||||||
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号推送
|
|
||||||
ja: Ansible mysql モジュールを使用してアカウントをプッシュする
|
|
||||||
en: Using Ansible module mysql to push account
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
- hosts: oracle
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test Oracle connection
|
|
||||||
oracle_ping:
|
|
||||||
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 }}"
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: Display Oracle version
|
|
||||||
debug:
|
|
||||||
var: db_info.server_version
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Change Oracle password
|
|
||||||
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 }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
ignore_errors: true
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
oracle_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
mode: "{{ account.mode }}"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: push_account_oracle
|
|
||||||
name: "{{ 'Oracle account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- oracle
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account push:
|
|
||||||
zh: 使用 Python 模块 oracledb 执行 Oracle 账号推送
|
|
||||||
ja: Python oracledb モジュールを使用してアカウントをプッシュする
|
|
||||||
en: Using Python module oracledb to push account
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
- hosts: postgre
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test PostgreSQL connection
|
|
||||||
community.postgresql.postgresql_ping:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|
||||||
- name: Display PostgreSQL version
|
|
||||||
debug:
|
|
||||||
var: result.server_version.full
|
|
||||||
when: result is succeeded
|
|
||||||
|
|
||||||
- name: Change PostgreSQL password
|
|
||||||
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 }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
role_attr_flags: LOGIN
|
|
||||||
ignore_errors: true
|
|
||||||
when: result is succeeded
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
community.postgresql.postgresql_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
when:
|
|
||||||
- result is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: push_account_postgresql
|
|
||||||
name: "{{ 'PostgreSQL account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- postgresql
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account push:
|
|
||||||
zh: 使用 Ansible 模块 postgresql 执行 PostgreSQL 账号推送
|
|
||||||
ja: Ansible postgresql モジュールを使用してアカウントをプッシュする
|
|
||||||
en: Using Ansible module postgresql to push account
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
- hosts: sqlserver
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test SQLServer connection
|
|
||||||
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: |
|
|
||||||
SELECT @@version
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: SQLServer version
|
|
||||||
set_fact:
|
|
||||||
info:
|
|
||||||
version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}"
|
|
||||||
- debug:
|
|
||||||
var: info
|
|
||||||
|
|
||||||
- name: Check whether SQLServer User exist
|
|
||||||
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: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
|
|
||||||
when: db_info is succeeded
|
|
||||||
register: user_exist
|
|
||||||
|
|
||||||
- name: Change SQLServer password
|
|
||||||
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: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
|
|
||||||
ignore_errors: true
|
|
||||||
when: user_exist.query_results[0] | length != 0
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Add SQLServer user
|
|
||||||
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: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
|
|
||||||
ignore_errors: true
|
|
||||||
when: user_exist.query_results[0] | length == 0
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
|
||||||
script: |
|
|
||||||
SELECT @@version
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: push_account_sqlserver
|
|
||||||
name: "{{ 'SQLServer account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- sqlserver
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
SQLServer account push:
|
|
||||||
zh: 使用 Ansible 模块 mssql 执行 SQLServer 账号推送
|
|
||||||
ja: Ansible mssql モジュールを使用してアカウントをプッシュする
|
|
||||||
en: Using Ansible module mssql to push account
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: "Test privileged {{ jms_account.username }} account"
|
|
||||||
ansible.builtin.ping:
|
|
||||||
|
|
||||||
- name: "Check if {{ account.username }} user exists"
|
|
||||||
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:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
|
||||||
update_password: always
|
|
||||||
ignore_errors: true
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} SSH key"
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- user_info.failed
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} password (paramiko)"
|
|
||||||
ssh_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
|
||||||
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"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
|
||||||
ssh_ping:
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
id: push_account_aix
|
|
||||||
name: "{{ 'Aix account push' | trans }}"
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- AIX
|
|
||||||
method: push_account
|
|
||||||
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:
|
|
||||||
Aix account push:
|
|
||||||
zh: '使用 Ansible 模块 user 执行 Aix 账号推送 (DES)'
|
|
||||||
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
|
|
||||||
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'
|
|
||||||
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: "Test privileged {{ jms_account.username }} account"
|
|
||||||
ansible.builtin.ping:
|
|
||||||
|
|
||||||
- name: "Check if {{ account.username }} user exists"
|
|
||||||
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:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
|
||||||
update_password: always
|
|
||||||
ignore_errors: true
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} SSH key"
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- user_info.failed
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} password (paramiko)"
|
|
||||||
ssh_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
|
||||||
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"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
|
||||||
ssh_ping:
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
id: push_account_posix
|
|
||||||
name: "{{ 'Posix account push' | trans }}"
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- unix
|
|
||||||
- linux
|
|
||||||
method: push_account
|
|
||||||
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:
|
|
||||||
Posix account push:
|
|
||||||
zh: '使用 Ansible 模块 user 执行账号推送 (sha512)'
|
|
||||||
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
|
|
||||||
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'
|
|
||||||
@@ -1,31 +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
|
|
||||||
ansible.windows.win_ping:
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_password: "{{ account.secret }}"
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
id: push_account_local_windows
|
|
||||||
name: "{{ 'Windows account push' | trans }}"
|
|
||||||
version: 1
|
|
||||||
method: push_account
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- windows
|
|
||||||
params:
|
|
||||||
- name: groups
|
|
||||||
type: str
|
|
||||||
label: '用户组'
|
|
||||||
default: 'Users,Remote Desktop Users'
|
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account push:
|
|
||||||
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送'
|
|
||||||
ja: 'Ansible win_user モジュールを使用して 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)'
|
|
||||||
@@ -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
|
|
||||||
@@ -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)'
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
from accounts.const import AutomationTypes
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes, SecretType
|
||||||
|
from accounts.models import Account
|
||||||
|
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 +13,109 @@ 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 create_nonlocal_accounts(self, accounts, snapshot_account_usernames, asset):
|
||||||
|
secret_type = self.secret_type
|
||||||
|
usernames = accounts.filter(secret_type=secret_type).values_list(
|
||||||
|
'username', flat=True
|
||||||
|
)
|
||||||
|
create_usernames = set(snapshot_account_usernames) - set(usernames)
|
||||||
|
create_account_objs = [
|
||||||
|
Account(
|
||||||
|
name=f'{username}-{secret_type}', username=username,
|
||||||
|
secret_type=secret_type, asset=asset,
|
||||||
|
)
|
||||||
|
for username in create_usernames
|
||||||
|
]
|
||||||
|
Account.objects.bulk_create(create_account_objs)
|
||||||
|
|
||||||
|
def get_accounts(self, privilege_account, accounts: QuerySet):
|
||||||
|
if not privilege_account:
|
||||||
|
print(f'not privilege account')
|
||||||
|
return []
|
||||||
|
snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
|
if '*' in snapshot_account_usernames:
|
||||||
|
return accounts.exclude(username=privilege_account.username)
|
||||||
|
|
||||||
|
asset = privilege_account.asset
|
||||||
|
self.create_nonlocal_accounts(accounts, snapshot_account_usernames, asset)
|
||||||
|
accounts = asset.accounts.exclude(username=privilege_account.username).filter(
|
||||||
|
username__in=snapshot_account_usernames, secret_type=self.secret_type
|
||||||
|
)
|
||||||
|
return accounts
|
||||||
|
|
||||||
|
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 = asset.accounts.all()
|
||||||
|
accounts = self.get_accounts(account, accounts)
|
||||||
|
inventory_hosts = []
|
||||||
|
host['secret_type'] = self.secret_type
|
||||||
|
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
||||||
|
msg = f'Windows {asset} does not support ssh key push \n'
|
||||||
|
print(msg)
|
||||||
|
return inventory_hosts
|
||||||
|
|
||||||
|
for account in accounts:
|
||||||
|
h = deepcopy(host)
|
||||||
|
h['name'] += '(' + account.username + ')'
|
||||||
|
new_secret = self.get_secret()
|
||||||
|
|
||||||
|
self.name_recorder_mapper[h['name']] = {
|
||||||
|
'account': account, 'new_secret': new_secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
private_key_path = None
|
||||||
|
if self.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['kwargs'] = self.get_kwargs(account, new_secret)
|
||||||
|
h['account'] = {
|
||||||
|
'name': account.name,
|
||||||
|
'username': account.username,
|
||||||
|
'secret_type': account.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'])
|
||||||
|
|
||||||
|
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 not self.check_secret():
|
||||||
|
return
|
||||||
|
super().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(
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
- hosts: mysql
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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 }}"
|
|
||||||
@@ -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
|
|
||||||
@@ -1,26 +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 != ""
|
|
||||||
|
|
||||||
- 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 != ""
|
|
||||||
|
|
||||||
- name: "Remove account"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
remove: "{{ home_dir.stat.exists }}"
|
|
||||||
when: home_dir.stat | default(false)
|
|
||||||
@@ -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
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
- hosts: windows
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
ansible.windows.win_user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
purge: yes
|
|
||||||
force: yes
|
|
||||||
@@ -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
|
|
||||||
@@ -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')
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
- hosts: custom
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_shell_type: sh
|
|
||||||
ansible_connection: local
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Verify account (pyfreerdp)
|
|
||||||
rdp_ping:
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user