Compare commits

..

27 Commits

Author SHA1 Message Date
fit2bot
97b78e34a0 feat: Update v3.0.3 2023-03-02 17:08:04 +08:00
fit2bot
8c4e496391 fix: 修复i8n 500 (#9848)
Co-authored-by: feng <1304903146@qq.com>
2023-03-02 16:23:02 +08:00
Bai
fad214ccbb fix: 修复 ldap 用户登录时邮箱存在 500 的问题 2023-03-02 16:22:24 +08:00
Bai
3b0f68c4c7 fix: 修复 ldap 用户登录时邮箱存在 500 的问题 2023-03-02 16:11:04 +08:00
Aaron3S
87b4ecfb3a fix: 修复作业执行没有日志权限的问题 2023-03-01 18:34:55 +08:00
Bai
32f2be2793 fix: 资产类型树返回类型节点时, 没有platfrom设置isParent为False, 解决展开节点重复的问题 2023-03-01 17:28:59 +08:00
fit2bot
a098bc6c06 perf: 推送账号 社区版定时任务关闭 (#9805)
Co-authored-by: feng <1304903146@qq.com>
2023-02-28 13:43:38 +08:00
老广
86870ad985 Merge pull request #9798 from jumpserver/pr@v3.0@fix_protocol_init_error
perf: 修改协议创建时一些默认值
2023-02-28 09:45:15 +08:00
ibuler
c602bf7224 perf: 修改协议创建时一些默认值 2023-02-27 11:49:04 +00:00
fit2bot
86dab4fc6e perf: 今日活跃资产 (#9797)
Co-authored-by: feng <1304903146@qq.com>
2023-02-27 18:10:11 +08:00
Aaron3S
a85a80a945 fix: 默认增加普通用户作业中心权限 2023-02-27 17:28:04 +08:00
老广
349edc10aa Merge pull request #9791 from jumpserver/pr@v3.0@add_accounts_suggestions
perf: 添加账号用户名的推荐
2023-02-27 15:19:26 +08:00
ibuler
44918e3cb5 perf: 添加账号用户名的推荐
perf: 修改账号推荐
2023-02-27 07:14:55 +00:00
ibuler
9a2f6c0d70 perf: 修改资产 address 长度,以支持 mb4
perf: 修改长度
2023-02-27 14:08:15 +08:00
ibuler
934969a8f1 perf: 去掉没有 Name 的迁移 2023-02-27 14:02:09 +08:00
老广
57162c1628 Merge pull request #9776 from jumpserver/pr@v3.0@perf_account_migrate2
perf: 优化迁移 accounts
2023-02-27 10:22:59 +08:00
ibuler
32fb36867f perf: 优化迁移 accounts
perf: 优化账号迁移,同名的迁移到历史中
2023-02-26 01:49:25 +00:00
老广
158b589028 Merge pull request #9761 from jumpserver/pr@v3@fix_activity_save_error
fix: 解决Activity保存因为参数出错问题
2023-02-24 18:18:03 +08:00
jiangweidong
d64277353c Merge branch 'v3.0' of http://github.com/jumpserver/jumpserver into pr@v3@fix_activity_save_error 2023-02-24 18:10:47 +08:00
jiangweidong
bff6f397ce fix: 解决Activity保存因为参数出错问题 2023-02-24 18:10:42 +08:00
fit2bot
0ad461a804 perf: 修改host info 接口, 社区开放applet, 修改改密发邮件bug (#9760)
Co-authored-by: feng <1304903146@qq.com>
2023-02-24 18:08:40 +08:00
Bai
a1dcef0ba0 fix: 修复 web gui 支持的数据库 2023-02-24 15:12:08 +08:00
Bai
dbb1ee3a75 fix: 修复认证MFA失败次数清空问题 2023-02-24 14:43:51 +08:00
fit2bot
d6bd207a17 fix: 修复计算今日活跃资产过滤逻辑 (#9744)
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-02-24 12:17:10 +08:00
Bai
e69ba27ff4 fix: 修复获取授权资产详情时返回 spec_info 字段, 解决连接 Magnus 问题 2023-02-24 11:41:47 +08:00
ibuler
adbe7c07c6 perf: 修复社区版可能引起的问题 2023-02-24 00:31:10 +08:00
老广
d1eacf53d4 Merge pull request #9736 from jumpserver/dev
fix: 修复 loong64 grpc 构建失败
2023-02-23 21:50:11 +08:00
714 changed files with 8509 additions and 27317 deletions

View File

@@ -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/

View File

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

View File

@@ -3,13 +3,11 @@ name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进 about: 提交产品缺陷帮助我们更好的改进
title: "[Bug] " title: "[Bug] "
labels: 类型:bug labels: 类型:bug
assignees: assignees: wojiushixiaobai
- wojiushixiaobai
- baijiangjie
--- ---
**JumpServer 版本( v2.28 之前的版本不再支持 )** **JumpServer 版本(v1.5.9以下不再支持)**
**浏览器版本** **浏览器版本**

View File

@@ -3,9 +3,7 @@ name: 问题咨询
about: 提出针对本项目安装部署、使用及其他方面的相关问题 about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[Question] " title: "[Question] "
labels: 类型:提问 labels: 类型:提问
assignees: assignees: wojiushixiaobai
- wojiushixiaobai
- baijiangjie
--- ---

View File

@@ -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'

View File

@@ -24,7 +24,6 @@ jobs:
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

View File

@@ -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'

2
.gitignore vendored
View File

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

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-bullseye as stage-build FROM python:3.9-slim as stage-build
ARG TARGETARCH ARG TARGETARCH
ARG VERSION ARG VERSION
@@ -8,8 +8,9 @@ WORKDIR /opt/jumpserver
ADD . . ADD . .
RUN cd utils && bash -ixeu build.sh RUN cd utils && bash -ixeu build.sh
FROM python:3.11-slim-bullseye FROM python:3.9-slim
ARG TARGETARCH ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \ ARG BUILD_DEPENDENCIES=" \
g++ \ g++ \
@@ -21,14 +22,11 @@ ARG DEPENDENCIES=" \
libpq-dev \ libpq-dev \
libffi-dev \ libffi-dev \
libjpeg-dev \ libjpeg-dev \
libkrb5-dev \
libldap2-dev \ libldap2-dev \
libsasl2-dev \ libsasl2-dev \
libssl-dev \
libxml2-dev \ libxml2-dev \
libxmlsec1-dev \ libxmlsec1-dev \
libxmlsec1-openssl \ libxmlsec1-openssl \
freerdp2-dev \
libaio-dev" libaio-dev"
ARG TOOLS=" \ ARG TOOLS=" \
@@ -37,11 +35,13 @@ ARG TOOLS=" \
default-libmysqlclient-dev \ default-libmysqlclient-dev \
default-mysql-client \ default-mysql-client \
locales \ locales \
nmap \
openssh-client \ openssh-client \
procps \
sshpass \ sshpass \
telnet \ telnet \
unzip \
vim \ vim \
git \
wget" wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn ARG APT_MIRROR=http://mirrors.ustc.edu.cn
@@ -55,7 +55,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \ && apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \ && mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \
&& echo "set mouse-=a" > ~/.vimrc \ && echo "set mouse-=a" > ~/.vimrc \
&& echo "no" | dpkg-reconfigure dash \ && echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
@@ -63,17 +63,37 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
&& sed -i "s@# alias @alias @g" ~/.bashrc \ && sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver ARG DOWNLOAD_URL=https://download.jumpserver.org
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple RUN mkdir -p /opt/oracle/ \
RUN --mount=type=cache,target=/root/.cache \ && 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 \ set -ex \
&& echo > /opt/jumpserver/config.yml \ && pip config set global.index-url ${PIP_MIRROR} \
&& pip install poetry -i ${PIP_MIRROR} \ && pip install --upgrade pip \
&& poetry config virtualenvs.create false \ && pip install --upgrade setuptools wheel \
&& poetry install --only=main && 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/data
VOLUME /opt/jumpserver/logs VOLUME /opt/jumpserver/logs

View File

@@ -1,9 +1,10 @@
ARG VERSION ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM jumpserver/core:${VERSION} FROM jumpserver/core:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
RUN --mount=type=cache,target=/root/.cache \ WORKDIR /opt/jumpserver
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \ set -ex \
&& poetry install --only=xpack && pip install -r requirements/requirements_xpack.txt

96
Dockerfile.loong64 Normal file
View File

@@ -0,0 +1,96 @@
FROM python:3.9-slim as stage-build
ARG TARGETARCH
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
freetds-dev \
libpq-dev \
libffi-dev \
libjpeg-dev \
libldap2-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libaio-dev"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
locales \
openssh-client \
procps \
sshpass \
telnet \
unzip \
vim \
git \
wget"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
set -ex \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /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"]

2
GITSHA
View File

@@ -1 +1 @@
180cf354ad205c69fbb915eb49d906ccc31ed118 8c4e496391a7f7e96af50ee45c9b013700a2e30c

View File

@@ -10,27 +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">
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
<br>
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 连接各类应用。
## 产品特色 ## 产品特色
- **开源**: 零门槛,线上快速获取和安装; - **开源**: 零门槛,线上快速获取和安装;
@@ -39,10 +22,12 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
- **多云支持**: 一套系统,同时管理不同云上面的资产; - **多云支持**: 一套系统,同时管理不同云上面的资产;
- **多租户**: 一套系统,多个子公司或部门同时使用; - **多租户**: 一套系统,多个子公司或部门同时使用;
- **云端存储**: 审计录像云端存储,永不丢失; - **云端存储**: 审计录像云端存储,永不丢失;
- **多应用支持**: 全面支持各类资产包括服务器、数据库、Windows RemoteApp、Kubernetes 等;
- **安全可靠**: 被广泛使用、验证和信赖,连续 9 年的持续研发投入和产品更新升级。
## UI 展示 ## UI 展示
![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png) ![UI展示](https://www.jumpserver.org/images/screenshot/1.png)
## 在线体验 ## 在线体验
@@ -56,9 +41,9 @@ 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)
## 案例研究 ## 案例研究
@@ -76,30 +61,30 @@ 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 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 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 项目 | | [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 客户端 项目 | | [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 安装包 项目 | | [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,6 +96,11 @@ 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-2023 飞致云 FIT2CLOUD, All rights reserved. Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.

View File

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

View File

@@ -1,39 +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.models import Account from accounts.models import Account
from assets.models import Asset, Node from assets.models import Asset, Node
from common.api import ExtraFilterFieldsMixin from common.permissions import UserConfirmation, ConfirmType
from common.permissions import UserConfirmation, ConfirmType, IsValidUser
from common.views.mixins import RecordViewLogMixin from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
__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):
@@ -47,27 +43,23 @@ 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')
assets = Asset.objects.all() assets = Asset.objects.all()
if asset_ids: if asset_ids:
assets = assets.filter(id__in=asset_ids) assets = assets.filter(id__in=asset_ids.split(','))
if node_ids: if node_keys:
nodes = Node.objects.filter(id__in=node_ids) patten = Node.get_node_all_children_key_pattern(node_keys.split(','))
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) assets = assets.filter(nodes__key__regex=patten)
assets = assets.filter(id__in=set(list(asset_ids) + list(node_asset_ids)))
accounts = Account.objects.filter(asset__in=assets) accounts = Account.objects.filter(asset__in=assets)
if username: if username:
@@ -79,12 +71,6 @@ 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(RecordViewLogMixin, AccountViewSet):
""" """
@@ -101,21 +87,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, 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, RecordViewLogMixin, ListAPIView):
model = Account.history.model model = Account.history.model
serializer_class = serializers.AccountHistorySerializer serializer_class = serializers.AccountHistorySerializer
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']
@@ -127,20 +99,14 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, List
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
if account.secret != latest_history.secret:
return histories
if account.secret_type != latest_history.secret_type:
return histories
histories = histories.exclude(history_id=latest_history.history_id)
return histories return histories
if account.secret == last_history.secret \
and account.secret_type == last_history.secret_type:
histories = histories.exclude(history_id=last_history.history_id)
return histories

View File

@@ -24,16 +24,15 @@ class AccountsTaskCreateAPI(CreateAPIView):
def perform_create(self, serializer): def perform_create(self, serializer):
data = serializer.validated_data data = serializer.validated_data
accounts = data.get('accounts', []) accounts = data.get('accounts', [])
params = data.get('params')
account_ids = [str(a.id) for a in accounts] account_ids = [str(a.id) for a in accounts]
if data['action'] == 'push': if data['action'] == 'push':
task = push_accounts_to_assets_task.delay(account_ids, params) task = push_accounts_to_assets_task.delay(account_ids)
else: else:
account = accounts[0] account = accounts[0]
asset = account.asset asset = account.asset
if not asset.auto_config['ansible_enabled'] or \ if not asset.auto_info['ansible_enabled'] or \
not asset.auto_config['ping_enabled']: not asset.auto_info['ping_enabled']:
raise NotSupportedTemporarilyError() raise NotSupportedTemporarilyError()
task = verify_accounts_connectivity_task.delay(account_ids) task = verify_accounts_connectivity_task.delay(account_ids)

View File

@@ -1,58 +1,19 @@
from django_filters import rest_framework as drf_filters from rbac.permissions import RBACPermission
from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers
from accounts.models import AccountTemplate
from assets.const import Protocol
from common.drf.filters import BaseFilterSet
from common.permissions import UserConfirmation, ConfirmType from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission from accounts import serializers
from accounts.models import AccountTemplate
class AccountTemplateFilterSet(BaseFilterSet):
protocols = drf_filters.CharFilter(method='filter_protocols')
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',
}
@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)
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet): class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):

View File

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

View File

@@ -26,8 +26,8 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet):
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet): class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AccountBackupPlanExecutionSerializer serializer_class = serializers.AccountBackupPlanExecutionSerializer
search_fields = ('trigger', 'plan__name') search_fields = ('trigger',)
filterset_fields = ('trigger', 'plan_id', 'plan__name') filterset_fields = ('trigger', 'plan_id')
http_method_names = ['get', 'post', 'options'] http_method_names = ['get', 'post', 'options']
def get_queryset(self): def get_queryset(self):

View File

@@ -1,5 +1,5 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import status, mixins, viewsets from rest_framework import status, mixins, viewsets
from rest_framework.response import Response from rest_framework.response import Response
@@ -95,8 +95,8 @@ class AutomationExecutionViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet mixins.RetrieveModelMixin, viewsets.GenericViewSet
): ):
search_fields = ('trigger', 'automation__name') search_fields = ('trigger',)
filterset_fields = ('trigger', 'automation_id', 'automation__name') filterset_fields = ('trigger', 'automation_id')
serializer_class = serializers.AutomationExecutionSerializer serializer_class = serializers.AutomationExecutionSerializer
tp: str tp: str

View File

@@ -1,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
@@ -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)

View File

@@ -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

View File

@@ -1,17 +1,22 @@
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 openpyxl import Workbook from django.db.models import F
from rest_framework import serializers from rest_framework import serializers
from accounts.notifications import AccountBackupExecutionTaskMsg from accounts.models import Account
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 from accounts.serializers import AccountSecretSerializer
from common.utils.timezone import local_now_display from accounts.notifications import AccountBackupExecutionTaskMsg
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')
@@ -71,22 +76,8 @@ class AssetAccountHandler(BaseAccountHandler):
) )
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():
@@ -106,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
@@ -119,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'
'' ''
@@ -129,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
@@ -143,14 +133,14 @@ class AccountBackupHandler:
wb.save(filename) wb.save(filename)
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'
'' ''
@@ -165,7 +155,7 @@ class AccountBackupHandler:
encrypt_and_compress_zip_file(attachment, password, files) 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: for file in files:
os.remove(file) os.remove(file)
@@ -173,42 +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('已完成对任务状态的更新') 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:
recipients_part_one = self.execution.snapshot.get('recipients_part_one', []) recipients = self.execution.plan_snapshot.get('recipients')
recipients_part_two = self.execution.snapshot.get('recipients_part_two', []) if not recipients:
if not recipients_part_one and not recipients_part_two: logger.info(
print(
'\n' '\n'
'\033[32m>>> 该备份任务未分配收件人\033[0m' '\033[32m>>> 该备份任务未分配收件人\033[0m'
'' ''
) )
if recipients_part_one and recipients_part_two:
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: else:
recipients = recipients_part_one or recipients_part_two
files = self.create_excel() files = self.create_excel()
self.send_backup_mail(files, recipients) 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
@@ -218,15 +199,15 @@ class AccountBackupHandler:
self.step_finished(is_success) self.step_finished(is_success)
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('用时: {}'.format(timedelta)) logger.info('用时: {}'.format(timedelta))

View File

@@ -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()

View File

@@ -1,50 +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) }}"
register: ping_info
- 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
- 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: false

View File

@@ -1,20 +0,0 @@
id: change_secret_by_ssh
name: "{{ 'SSH account change secret' | trans }}"
category:
- device
- host
type:
- all
method: change_secret
params:
- name: commands
type: list
label: '自定义命令'
default: [ '' ]
help_text: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
i18n:
SSH account change secret:
zh: 使用 SSH 命令行自定义改密
ja: SSH コマンドライン方式でカスタムパスワード変更
en: Custom password change by SSH command line

View File

@@ -38,8 +38,8 @@
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:
@@ -53,3 +53,6 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key }}" ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options: connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -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

View File

@@ -28,8 +28,8 @@
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:
@@ -38,3 +38,6 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
filter: version filter: version
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -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

View File

@@ -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,3 +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 }}"
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -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 アカウントのパスワード変更

View File

@@ -29,8 +29,8 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret }}" password: "{{ account.secret }}"
role_attr_flags: LOGIN role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded when: result is succeeded
register: change_info
- name: Verify password - name: Verify password
community.postgresql.postgresql_ping: community.postgresql.postgresql_ping:
@@ -39,3 +39,8 @@
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}" db: "{{ jms_asset.spec_info.db_name }}"
when:
- result is succeeded
- change_info is succeeded
register: result
failed_when: not result.is_available

View File

@@ -1,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 アカウントのパスワード変更

View File

@@ -41,8 +41,8 @@
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 }}'; 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:
@@ -52,8 +52,8 @@
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 }}'; select @@version" script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0 when: user_exist.query_results[0] | length == 0
register: change_info
- name: Verify password - name: Verify password
community.general.mssql_script: community.general.mssql_script:
@@ -64,3 +64,6 @@
name: '{{ jms_asset.spec_info.db_name }}' name: '{{ jms_asset.spec_info.db_name }}'
script: | script: |
SELECT @@version SELECT @@version
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -1,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 アカウントのパスワード変更

View File

@@ -1,96 +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: false when: secret_type == "password"
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)" - name: Verify SSH key
ssh_ping: ansible.builtin.ping:
login_host: "{{ jms_asset.address }}" become: no
login_port: "{{ jms_asset.port }}" vars:
login_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
become: false when: secret_type == "ssh_key"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -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'

View File

@@ -1,96 +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: false when: secret_type == "password"
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)" - name: Verify SSH key
ssh_ping: ansible.builtin.ping:
login_host: "{{ jms_asset.address }}" become: no
login_port: "{{ jms_asset.port }}" vars:
login_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}" ansible_become: no
become: false when: secret_type == "ssh_key"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -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'

View File

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

View File

@@ -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)'

View File

@@ -12,7 +12,7 @@ from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg 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_display from common.utils.timezone import local_now_display
from users.models import User from users.models import User
@@ -28,23 +28,23 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.method_hosts_mapper = defaultdict(list) 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'
@@ -54,34 +54,18 @@ class ChangeSecretManager(AccountBasePlaybookManager):
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,
@@ -94,10 +78,17 @@ 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 []
@@ -106,17 +97,16 @@ class ChangeSecretManager(AccountBasePlaybookManager):
method_hosts = [h for h in method_hosts if h != host['name']] method_hosts = [h for h in method_hosts if h != host['name']]
inventory_hosts = [] inventory_hosts = []
records = [] 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 + ')'
new_secret = self.get_secret(secret_type) new_secret = self.get_secret()
recorder = ChangeSecretRecord( recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution, asset=asset, account=account, execution=self.execution,
@@ -126,15 +116,15 @@ class ChangeSecretManager(AccountBasePlaybookManager):
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': new_secret, 'secret': new_secret,
'private_key_path': private_key_path 'private_key_path': private_key_path
} }

View File

@@ -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 アカウントの収集

View File

@@ -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 アカウントの収集

View File

@@ -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 アカウントの収集

View File

@@ -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 アカウントの収集

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -12,7 +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.is_sync_account = self.execution.snapshot.get('is_sync_account')
@classmethod @classmethod
def method_type(cls): def method_type(cls):
@@ -23,41 +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
@staticmethod
def generate_data(asset, result):
data = []
for username, info in result.items():
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 update_or_create_accounts(self, asset, result): @staticmethod
data = self.generate_data(asset, result) def update_or_create_gathered_accounts(asset, result):
with tmp_to_org(asset.org_id): with tmp_to_org(asset.org_id):
gathered_accounts = []
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False) GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
for d in data: for username, data in result.items():
username = d['username'] d = {'asset': asset, 'username': username, 'present': True}
gathered_account, __ = GatheredAccount.objects.update_or_create( 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, defaults=d, asset=asset, username=username,
) )
gathered_accounts.append(gathered_account)
if not self.is_sync_account:
return
GatheredAccount.sync_accounts(gathered_accounts)
def on_host_success(self, host, result): def on_host_success(self, host, result):
info = result.get('debug', {}).get('res', {}).get('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.update_or_create_accounts(asset, result) self.update_or_create_gathered_accounts(asset, result)
else: else:
logger.error("Not found info".format(host)) logger.error("Not found info".format(host))

View File

@@ -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)

View File

@@ -1,55 +0,0 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/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 }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
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 }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
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 }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"

View File

@@ -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

View File

@@ -1,40 +0,0 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}"
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 }}"
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 }}"
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 }}"
filter: version

View File

@@ -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

View File

@@ -1,41 +0,0 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/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 }}"

View File

@@ -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

View File

@@ -1,44 +0,0 @@
- hosts: postgre
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/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
- 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

View File

@@ -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

View File

@@ -1,68 +0,0 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/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 }}'; 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 }}'; 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

View File

@@ -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

View File

@@ -1,97 +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: 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('') }}"
become: false
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -1,36 +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: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
- name: shell
type: str
label: 'Shell'
default: '/bin/bash'
- name: home
type: str
label: '家目录'
default: ''
help_text: '默认家目录 /home/系统用户名: /home/username'
- name: groups
type: str
label: '用户组'
default: ''
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n:
Aix account push:
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
en: Using Ansible module user to push account (DES)

View File

@@ -1,97 +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: 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('') }}"
become: false
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -1,37 +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: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
- name: shell
type: str
label: 'Shell'
default: '/bin/bash'
help_text: ''
- name: home
type: str
label: '家目录'
default: ''
help_text: '默认家目录 /home/系统用户名: /home/username'
- name: groups
type: str
label: '用户组'
default: ''
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n:
Posix account push:
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
en: Using Ansible module user to push account (sha512)

View File

@@ -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"

View File

@@ -1,19 +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: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n:
Windows account push:
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送
ja: Ansible win_user モジュールを使用して Windows アカウントをプッシュする
en: Using Ansible module win_user to push account

View File

@@ -1,6 +1,9 @@
from copy import deepcopy from copy import deepcopy
from accounts.const import AutomationTypes, SecretType, Connectivity from django.db.models import QuerySet
from accounts.const import AutomationTypes, SecretType
from accounts.models import Account
from assets.const import HostTypes 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
@@ -16,6 +19,36 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
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): def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super(ChangeSecretManager, self).host_callback( host = super(ChangeSecretManager, self).host_callback(
host, asset=asset, account=account, automation=automation, host, asset=asset, account=account, automation=automation,
@@ -24,37 +57,34 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
if host.get('error'): if host.get('error'):
return host return host
accounts = self.get_accounts(account) accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = [] inventory_hosts = []
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:
msg = f'Windows {asset} does not support ssh key push' msg = f'Windows {asset} does not support ssh key push \n'
print(msg) print(msg)
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)
self.name_recorder_mapper[h['name']] = { self.name_recorder_mapper[h['name']] = {
'account': account, 'new_secret': new_secret, 'account': account, 'new_secret': new_secret,
} }
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': new_secret, 'secret': new_secret,
'private_key_path': private_key_path 'private_key_path': private_key_path
} }
@@ -74,7 +104,6 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
return return
account.secret = new_secret account.secret = new_secret
account.save(update_fields=['secret']) account.save(update_fields=['secret'])
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result): def on_host_error(self, host, error, result):
pass pass
@@ -83,9 +112,9 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
logger.error("Pust account error: ", e) logger.error("Pust account error: ", e)
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(ChangeSecretManager, self).run(*args, **kwargs) super().run(*args, **kwargs)
# @classmethod # @classmethod
# def trigger_by_asset_create(cls, asset): # def trigger_by_asset_create(cls, asset):

View File

@@ -1,15 +0,0 @@
- hosts: custom
gather_facts: no
vars:
ansible_shell_type: sh
ansible_connection: local
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 }}"

View File

@@ -1,13 +0,0 @@
id: verify_account_by_rdp
name: "{{ 'Windows rdp account verify' | trans }}"
category:
- host
type:
- windows
method: verify_account
i18n:
Windows rdp account verify:
zh: 使用 Python 模块 pyfreerdp 验证账号
ja: Python モジュール pyfreerdp を使用してアカウントを検証する
en: Using Python module pyfreerdp to verify account

View File

@@ -1,20 +0,0 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
ansible_become: false
tasks:
- name: Verify account (paramiko)
ssh_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 }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"

View File

@@ -1,14 +0,0 @@
id: verify_account_by_ssh
name: "{{ 'SSH account verify' | trans }}"
category:
- device
- host
type:
- all
method: verify_account
i18n:
SSH account verify:
zh: 使用 Python 模块 paramiko 验证账号
ja: Python モジュール paramiko を使用してアカウントを検証する
en: Using Python module paramiko to verify account

View File

@@ -1,12 +1,6 @@
id: verify_account_mongodb id: verify_account_mongodb
name: "{{ 'MongoDB account verify' | trans }}" name: Verify account from MongoDB
category: database category: database
type: type:
- mongodb - mongodb
method: verify_account method: verify_account
i18n:
MongoDB account verify:
zh: 使用 Ansible 模块 mongodb 验证账号
ja: Ansible mongodb モジュールを使用してアカウントを検証する
en: Using Ansible module mongodb to verify account

View File

@@ -1,14 +1,7 @@
id: verify_account_mysql id: verify_account_mysql
name: "{{ 'MySQL account verify' | trans }}" name: Verify account from MySQL
category: database category: database
type: type:
- mysql - mysql
- mariadb - mariadb
method: verify_account method: verify_account
i18n:
MySQL account verify:
zh: 使用 Ansible 模块 mysql 验证账号
ja: Ansible mysql モジュールを使用してアカウントを検証する
en: Using Ansible module mysql to verify account

View File

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

View File

@@ -1,12 +1,6 @@
id: verify_account_postgresql id: verify_account_postgresql
name: "{{ 'PostgreSQL account verify' | trans }}" name: Verify account for PostgreSQL
category: database category: database
type: type:
- postgresql - postgresql
method: verify_account method: verify_account
i18n:
PostgreSQL account verify:
zh: 使用 Ansible 模块 postgresql 验证账号
ja: Ansible postgresql モジュールを使用してアカウントを検証する
en: Using Ansible module postgresql to verify account

View File

@@ -1,12 +1,6 @@
id: verify_account_sqlserver id: verify_account_sqlserver
name: "{{ 'SQLServer account verify' | trans }}" name: Verify account from SQLServer
category: database category: database
type: type:
- sqlserver - sqlserver
method: verify_account method: verify_account
i18n:
SQLServer account verify:
zh: 使用 Ansible 模块 mssql 验证账号
ja: Ansible mssql モジュールを使用してアカウントを検証する
en: Using Ansible module mssql to verify account

View File

@@ -1,13 +1,7 @@
id: verify_account_posix id: verify_account_posix
name: "{{ 'Posix account verify' | trans }}" name: Verify posix account
category: host category: host
type: type:
- linux - linux
- unix - unix
method: verify_account method: verify_account
i18n:
Posix account verify:
zh: 使用 Ansible 模块 ping 验证账号
ja: Ansible ping モジュールを使用してアカウントを検証する
en: Using Ansible module ping to verify account

View File

@@ -1,9 +1,6 @@
- hosts: windows - hosts: windows
gather_facts: no gather_facts: no
tasks: tasks:
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify account - name: Verify account
ansible.windows.win_ping: ansible.windows.win_ping:
vars: vars:

View File

@@ -1,13 +1,7 @@
id: verify_account_windows id: verify_account_windows
name: "{{ 'Windows account verify' | trans }}" name: Verify account windows
version: 1 version: 1
method: verify_account method: verify_account
category: host category: host
type: type:
- windows - windows
i18n:
Windows account verify:
zh: 使用 Ansible 模块 win_ping 验证账号
ja: Ansible win_ping モジュールを使用してアカウントを検証する
en: Using Ansible module win_ping to verify account

View File

@@ -25,15 +25,6 @@ class VerifyAccountManager(AccountBasePlaybookManager):
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n') f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
return path return path
@classmethod
def method_type(cls):
return AutomationTypes.verify_account
def get_accounts(self, privilege_account, accounts: QuerySet):
account_ids = self.execution.snapshot['accounts']
accounts = accounts.filter(id__in=account_ids)
return accounts
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs): def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super().host_callback( host = super().host_callback(
host, asset=asset, account=account, host, asset=asset, account=account,
@@ -71,6 +62,16 @@ class VerifyAccountManager(AccountBasePlaybookManager):
inventory_hosts.append(h) inventory_hosts.append(h)
return inventory_hosts return inventory_hosts
@classmethod
def method_type(cls):
return AutomationTypes.verify_account
def get_accounts(self, privilege_account, accounts: QuerySet):
snapshot_account_usernames = self.execution.snapshot['accounts']
if '*' not in snapshot_account_usernames:
accounts = accounts.filter(username__in=snapshot_account_usernames)
return accounts
def on_host_success(self, host, result): def on_host_success(self, host, result):
account = self.host_account_mapper.get(host) account = self.host_account_mapper.get(host)
account.set_connectivity(Connectivity.OK) account.set_connectivity(Connectivity.OK)

View File

@@ -1,6 +1,6 @@
from common.utils import get_logger
from accounts.const import AutomationTypes from accounts.const import AutomationTypes
from assets.automations.ping_gateway.manager import PingGatewayManager from assets.automations.ping_gateway.manager import PingGatewayManager
from common.utils import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -16,6 +16,6 @@ class VerifyGatewayAccountManager(PingGatewayManager):
logger.info(">>> 开始执行测试网关账号可连接性任务") logger.info(">>> 开始执行测试网关账号可连接性任务")
def get_accounts(self, gateway): def get_accounts(self, gateway):
account_ids = self.execution.snapshot['accounts'] usernames = self.execution.snapshot['accounts']
accounts = gateway.accounts.filter(id__in=account_ids) accounts = gateway.accounts.filter(username__in=usernames)
return accounts return accounts

View File

@@ -1,41 +0,0 @@
from importlib import import_module
from django.utils.functional import LazyObject
from common.utils import get_logger
from ..const import VaultTypeChoices
__all__ = ['vault_client', 'get_vault_client']
logger = get_logger(__file__)
def get_vault_client(raise_exception=False, **kwargs):
enabled = kwargs.get('VAULT_ENABLED')
tp = 'hcp' if enabled else 'local'
try:
module_path = f'apps.accounts.backends.{tp}.main'
client = import_module(module_path).Vault(**kwargs)
except Exception as e:
logger.error(f'Init vault client failed: {e}')
if raise_exception:
raise
tp = VaultTypeChoices.local
module_path = f'apps.accounts.backends.{tp}.main'
client = import_module(module_path).Vault(**kwargs)
return client
class VaultClient(LazyObject):
def _setup(self):
from jumpserver import settings as js_settings
from django.conf import settings
vault_config_names = [k for k in js_settings.__dict__.keys() if k.startswith('VAULT_')]
vault_configs = {name: getattr(settings, name, None) for name in vault_config_names}
self._wrapped = get_vault_client(**vault_configs)
""" 为了安全, 页面修改配置, 重启服务后才会重新初始化 vault_client """
vault_client = VaultClient()

View File

@@ -1,74 +0,0 @@
from abc import ABC, abstractmethod
from django.forms.models import model_to_dict
__all__ = ['BaseVault']
class BaseVault(ABC):
def __init__(self, *args, **kwargs):
self.enabled = kwargs.get('VAULT_ENABLED')
def get(self, instance):
""" 返回 secret 值 """
return self._get(instance)
def create(self, instance):
if not instance.secret_has_save_to_vault:
self._create(instance)
self._clean_db_secret(instance)
self.save_metadata(instance)
if instance.is_sync_metadata:
self.save_metadata(instance)
def update(self, instance):
if not instance.secret_has_save_to_vault:
self._update(instance)
self._clean_db_secret(instance)
self.save_metadata(instance)
if instance.is_sync_metadata:
self.save_metadata(instance)
def delete(self, instance):
self._delete(instance)
def save_metadata(self, instance):
metadata = model_to_dict(instance, fields=[
'name', 'username', 'secret_type',
'connectivity', 'su_from', 'privileged'
])
metadata = {k: str(v)[:500] for k, v in metadata.items() if v}
return self._save_metadata(instance, metadata)
# -------- abstractmethod -------- #
@abstractmethod
def _get(self, instance):
raise NotImplementedError
@abstractmethod
def _create(self, instance):
raise NotImplementedError
@abstractmethod
def _update(self, instance):
raise NotImplementedError
@abstractmethod
def _delete(self, instance):
raise NotImplementedError
@abstractmethod
def _clean_db_secret(self, instance):
raise NotImplementedError
@abstractmethod
def _save_metadata(self, instance, metadata):
raise NotImplementedError
@abstractmethod
def is_active(self, *args, **kwargs) -> (bool, str):
raise NotImplementedError

View File

@@ -1 +0,0 @@
from .main import *

View File

@@ -1,84 +0,0 @@
import sys
from abc import ABC
from common.db.utils import Encryptor
from common.utils import lazyproperty
current_module = sys.modules[__name__]
__all__ = ['build_entry']
class BaseEntry(ABC):
def __init__(self, instance):
self.instance = instance
@lazyproperty
def full_path(self):
path_base = self.path_base
path_spec = self.path_spec
path = f'{path_base}/{path_spec}'
return path
@property
def path_base(self):
path = f'orgs/{self.instance.org_id}'
return path
@property
def path_spec(self):
raise NotImplementedError
def to_internal_data(self):
secret = getattr(self.instance, '_secret', None)
if secret is not None:
secret = Encryptor(secret).encrypt()
data = {'secret': secret}
return data
@staticmethod
def to_external_data(data):
secret = data.pop('secret', None)
if secret is not None:
secret = Encryptor(secret).decrypt()
return secret
class AccountEntry(BaseEntry):
@property
def path_spec(self):
path = f'assets/{self.instance.asset_id}/accounts/{self.instance.id}'
return path
class AccountTemplateEntry(BaseEntry):
@property
def path_spec(self):
path = f'account-templates/{self.instance.id}'
return path
class HistoricalAccountEntry(BaseEntry):
@property
def path_base(self):
account = self.instance.instance
path = f'accounts/{account.id}/'
return path
@property
def path_spec(self):
path = f'histories/{self.instance.history_id}'
return path
def build_entry(instance) -> BaseEntry:
class_name = instance.__class__.__name__
entry_class_name = f'{class_name}Entry'
entry_class = getattr(current_module, entry_class_name, None)
if not entry_class:
raise Exception(f'Entry class {entry_class_name} is not found')
return entry_class(instance)

View File

@@ -1,53 +0,0 @@
from common.db.utils import get_logger
from .entries import build_entry
from .service import VaultKVClient
from ..base import BaseVault
__all__ = ['Vault']
logger = get_logger(__name__)
class Vault(BaseVault):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = VaultKVClient(
url=kwargs.get('VAULT_HCP_HOST'),
token=kwargs.get('VAULT_HCP_TOKEN'),
mount_point=kwargs.get('VAULT_HCP_MOUNT_POINT')
)
def is_active(self):
return self.client.is_active()
def _get(self, instance):
entry = build_entry(instance)
# TODO: get data 是不是层数太多了
data = self.client.get(path=entry.full_path).get('data', {})
data = entry.to_external_data(data)
return data
def _create(self, instance):
entry = build_entry(instance)
data = entry.to_internal_data()
self.client.create(path=entry.full_path, data=data)
def _update(self, instance):
entry = build_entry(instance)
data = entry.to_internal_data()
self.client.patch(path=entry.full_path, data=data)
def _delete(self, instance):
entry = build_entry(instance)
self.client.delete(path=entry.full_path)
def _clean_db_secret(self, instance):
instance.is_sync_metadata = False
instance.mark_secret_save_to_vault()
def _save_metadata(self, instance, metadata):
try:
entry = build_entry(instance)
self.client.update_metadata(path=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@@ -1,102 +0,0 @@
# -*- coding: utf-8 -*-
#
import hvac
from hvac import exceptions
from requests.exceptions import ConnectionError
from common.utils import get_logger
logger = get_logger(__name__)
__all__ = ['VaultKVClient']
class VaultKVClient(object):
max_versions = 20
def __init__(self, url, token, mount_point):
assert isinstance(self.max_versions, int) and self.max_versions >= 3, (
'max_versions must to be an integer that is greater than or equal to 3'
)
self.client = hvac.Client(url=url, token=token)
self.mount_point = mount_point
self.enable_secrets_engine_if_need()
def is_active(self):
try:
if not self.client.sys.is_initialized():
return False, 'Vault is not initialized'
if self.client.sys.is_sealed():
return False, 'Vault is sealed'
if not self.client.is_authenticated():
return False, 'Vault is not authenticated'
except ConnectionError as e:
logger.error(str(e))
return False, f'Vault is not reachable: {e}'
else:
return True, ''
def enable_secrets_engine_if_need(self):
secrets_engines = self.client.sys.list_mounted_secrets_engines()
mount_points = secrets_engines.keys()
if f'{self.mount_point}/' in mount_points:
return
self.client.sys.enable_secrets_engine(
backend_type='kv',
path=self.mount_point,
options={'version': 2} # TODO: version 是否从配置中读取?
)
self.client.secrets.kv.v2.configure(
max_versions=self.max_versions,
mount_point=self.mount_point
)
def get(self, path, version=None):
try:
response = self.client.secrets.kv.v2.read_secret_version(
path=path,
version=version,
mount_point=self.mount_point
)
except exceptions.InvalidPath as e:
return {}
data = response.get('data', {})
return data
def create(self, path, data: dict):
self._update_or_create(path=path, data=data)
def update(self, path, data: dict):
""" 未更新的数据会被删除 """
self._update_or_create(path=path, data=data)
def patch(self, path, data: dict):
""" 未更新的数据不会被删除 """
self.client.secrets.kv.v2.patch(
path=path,
secret=data,
mount_point=self.mount_point
)
def delete(self, path):
self.client.secrets.kv.v2.delete_metadata_and_all_versions(
path=path,
mount_point=self.mount_point,
)
def _update_or_create(self, path, data: dict):
self.client.secrets.kv.v2.create_or_update_secret(
path=path,
secret=data,
mount_point=self.mount_point
)
def update_metadata(self, path, metadata: dict):
try:
self.client.secrets.kv.v2.update_metadata(
path=path,
mount_point=self.mount_point,
custom_metadata=metadata
)
except exceptions.InvalidPath as e:
logger.error('Update metadata error: {}'.format(e))

View File

@@ -1 +0,0 @@
from .main import *

View File

@@ -1,36 +0,0 @@
from common.utils import get_logger
from ..base import BaseVault
logger = get_logger(__name__)
__all__ = ['Vault']
class Vault(BaseVault):
def is_active(self):
return True, ''
def _get(self, instance):
secret = getattr(instance, '_secret', None)
return secret
def _create(self, instance):
""" Ignore """
pass
def _update(self, instance):
""" Ignore """
pass
def _delete(self, instance):
""" Ignore """
pass
def _save_metadata(self, instance, metadata):
""" Ignore """
pass
def _clean_db_secret(self, instance):
""" Ignore *重要* 不能删除本地 secret """
pass

View File

@@ -1,3 +1,2 @@
from .account import * from .account import *
from .automation import * from .automation import *
from .vault import *

View File

@@ -1,5 +1,5 @@
from django.db.models import TextChoices from django.db.models import TextChoices
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class SecretType(TextChoices): class SecretType(TextChoices):
@@ -7,27 +7,14 @@ class SecretType(TextChoices):
SSH_KEY = 'ssh_key', _('SSH key') SSH_KEY = 'ssh_key', _('SSH key')
ACCESS_KEY = 'access_key', _('Access key') ACCESS_KEY = 'access_key', _('Access key')
TOKEN = 'token', _('Token') TOKEN = 'token', _('Token')
API_KEY = 'api_key', _("API key")
class AliasAccount(TextChoices): class AliasAccount(TextChoices):
ALL = '@ALL', _('All') ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input') INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user') USER = '@USER', _('Dynamic user')
ANON = '@ANON', _('Anonymous account')
@classmethod
def virtual_choices(cls):
return [(k, v) for k, v in cls.choices if k not in (cls.ALL,)]
class Source(TextChoices): class Source(TextChoices):
LOCAL = 'local', _('Local') LOCAL = 'local', _('Local')
COLLECTED = 'collected', _('Collected') COLLECTED = 'collected', _('Collected')
TEMPLATE = 'template', _('Template')
class AccountInvalidPolicy(TextChoices):
SKIP = 'skip', _('Skip')
UPDATE = 'update', _('Update')
ERROR = 'error', _('Failed')

View File

@@ -1,5 +1,5 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from assets.const import Connectivity from assets.const import Connectivity
from common.db.fields import TreeChoices from common.db.fields import TreeChoices
@@ -48,7 +48,7 @@ class SecretStrategy(models.TextChoices):
class SSHKeyStrategy(models.TextChoices): class SSHKeyStrategy(models.TextChoices):
add = 'add', _('Append SSH KEY') add = 'add', _('Append SSH KEY')
set = 'set', _('Empty and append SSH KEY') set = 'set', _('Empty and append SSH KEY')
set_jms = 'set_jms', _('Replace (Replace only keys pushed by JumpServer) ') set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ')
class TriggerChoice(models.TextChoices, TreeChoices): class TriggerChoice(models.TextChoices, TreeChoices):

View File

@@ -1,9 +0,0 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
__all__ = ['VaultTypeChoices']
class VaultTypeChoices(models.TextChoices):
local = 'local', _('Database')
hcp = 'hcp', _('HCP Vault')

View File

@@ -5,6 +5,7 @@ from django_filters import rest_framework as drf_filters
from assets.models import Node from assets.models import Node
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from .models import Account, GatheredAccount from .models import Account, GatheredAccount
@@ -13,8 +14,7 @@ class AccountFilterSet(BaseFilterSet):
hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact') hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
username = drf_filters.CharFilter(field_name="username", lookup_expr='exact') username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact') address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr='exact') asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact')
asset = drf_filters.CharFilter(field_name='asset', lookup_expr='exact')
assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact') assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
nodes = drf_filters.CharFilter(method='filter_nodes') nodes = drf_filters.CharFilter(method='filter_nodes')
node_id = drf_filters.CharFilter(method='filter_nodes') node_id = drf_filters.CharFilter(method='filter_nodes')
@@ -46,7 +46,7 @@ class AccountFilterSet(BaseFilterSet):
class Meta: class Meta:
model = Account model = Account
fields = ['id', 'asset', 'source_id', 'secret_type'] fields = ['id', 'asset_id']
class GatheredAccountFilterSet(BaseFilterSet): class GatheredAccountFilterSet(BaseFilterSet):

View File

@@ -1,14 +1,12 @@
# Generated by Django 3.2.14 on 2022-12-28 07:29 # Generated by Django 3.2.14 on 2022-12-28 07:29
import uuid
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
import common.db.encoder import common.db.encoder
import common.db.fields import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models
import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -31,16 +29,13 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id', ('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', ('connectivity', models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity')),
models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-',
max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField( ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')),
@@ -66,8 +61,7 @@ class Migration(migrations.Migration):
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('secret_type', models.CharField( ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('version', models.IntegerField(default=0, verbose_name='Version')), ('version', models.IntegerField(default=0, verbose_name='Version')),
('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_id', models.AutoField(primary_key=True, serialize=False)),
@@ -102,8 +96,7 @@ class Migration(migrations.Migration):
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField( ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')),

View File

@@ -1,13 +1,11 @@
# Generated by Django 3.2.16 on 2022-12-30 08:08 # Generated by Django 3.2.16 on 2022-12-30 08:08
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import common.db.encoder import common.db.encoder
import common.db.fields import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -55,8 +53,7 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.baseautomation')), primary_key=True, serialize=False, to='assets.baseautomation')),
('secret_type', models.CharField( ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
verbose_name='Secret type')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'), ('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
('random_one', 'All assets use the same random password'), ('random_one', 'All assets use the same random password'),
('random_all', ('random_all',
@@ -113,7 +110,7 @@ class Migration(migrations.Migration):
('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')), ('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='New secret')), ('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')), ('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')), ('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16)), ('status', models.CharField(default='pending', max_length=16)),
@@ -159,8 +156,7 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.baseautomation')), primary_key=True, serialize=False, to='assets.baseautomation')),
('secret_type', models.CharField( ('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
verbose_name='Secret type')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'), ('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
('random_one', 'All assets use the same random password'), ('random_one', 'All assets use the same random password'),
('random_all', ('random_all',

View File

@@ -1,69 +0,0 @@
# Generated by Django 3.2.16 on 2023-03-07 07:36
from django.db import migrations
from django.db.models import Q
def get_nodes_all_assets(apps, *nodes):
node_model = apps.get_model('assets', 'Node')
asset_model = apps.get_model('assets', 'Asset')
node_ids = set()
descendant_node_query = Q()
for n in nodes:
node_ids.add(n.id)
descendant_node_query |= Q(key__istartswith=f'{n.key}:')
if descendant_node_query:
_ids = node_model.objects.order_by().filter(descendant_node_query).values_list('id', flat=True)
node_ids.update(_ids)
return asset_model.objects.order_by().filter(nodes__id__in=node_ids).distinct()
def get_all_assets(apps, snapshot):
node_model = apps.get_model('assets', 'Node')
asset_model = apps.get_model('assets', 'Asset')
asset_ids = snapshot.get('assets', [])
node_ids = snapshot.get('nodes', [])
nodes = node_model.objects.filter(id__in=node_ids)
node_asset_ids = get_nodes_all_assets(apps, *nodes).values_list('id', flat=True)
asset_ids = set(list(asset_ids) + list(node_asset_ids))
return asset_model.objects.filter(id__in=asset_ids)
def migrate_account_usernames_to_ids(apps, schema_editor):
db_alias = schema_editor.connection.alias
execution_model = apps.get_model('accounts', 'AutomationExecution')
account_model = apps.get_model('accounts', 'Account')
executions = execution_model.objects.using(db_alias).all()
executions_update = []
for execution in executions:
snapshot = execution.snapshot
accounts = account_model.objects.none()
account_usernames = snapshot.get('accounts', [])
for asset in get_all_assets(apps, snapshot):
accounts = accounts | asset.accounts.all()
secret_type = snapshot.get('secret_type')
if secret_type:
ids = accounts.filter(
username__in=account_usernames,
secret_type=secret_type
).values_list('id', flat=True)
else:
ids = accounts.filter(
username__in=account_usernames
).values_list('id', flat=True)
snapshot['accounts'] = [str(_id) for _id in ids]
execution.snapshot = snapshot
executions_update.append(execution)
execution_model.objects.bulk_update(executions_update, ['snapshot'])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_alter_gatheredaccount_options'),
]
operations = [
migrations.RunPython(migrate_account_usernames_to_ids),
]

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