Compare commits

..

22 Commits

Author SHA1 Message Date
fit2bot
e4cf81d96c feat: Update v3.0.2 2023-02-28 18:55:15 +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
1059 changed files with 12636 additions and 55478 deletions

View File

@@ -1,4 +1,5 @@
.git
logs/*
data/*
.github
tmp/*
@@ -7,4 +8,4 @@ celerybeat.pid
### Vagrant ###
.vagrant/
apps/xpack/.git
.history/

View File

@@ -1,35 +1,10 @@
---
name: 需求建议
about: 提出针对本项目的想法和建议
title: "[Feature] 需求标题"
title: "[Feature] "
labels: 类型:需求
assignees:
- ibuler
- baijiangjie
assignees: ibuler
---
## 注意
_针对过于简单的需求描述不予考虑。请确保提供足够的细节和信息以支持功能的开发和实现。_
## 功能名称
[在这里输入功能的名称或标题]
## 功能描述
[在这里描述该功能的详细内容,包括其作用、目的和所需的功能]
## 用户故事(可选)
[如果适用,可以提供用户故事来更好地理解该功能的使用场景和用户期望]
## 功能要求
- [要求1描述该功能的具体要求如界面设计、交互逻辑等]
- [要求2描述该功能的另一个具体要求]
- [以此类推,列出所有相关的功能要求]
## 示例或原型(可选)
[如果有的话,提供该功能的示例或原型图以更好地说明功能的实现方式]
## 优先级
[描述该功能的优先级,如高、中、低,或使用数字等其他标识]
## 备注(可选)
[在这里添加任何其他相关信息或备注]
**请描述您的需求或者改进建议.**

View File

@@ -1,51 +1,22 @@
---
name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[Bug] Bug 标题"
labels: 类型:Bug
assignees:
- baijiangjie
title: "[Bug] "
labels: 类型:bug
assignees: wojiushixiaobai
---
## 注意
**JumpServer 版本( v2.28 之前的版本不再支持 )** <br>
_针对过于简单的 Bug 描述不予考虑。请确保提供足够的细节和信息以支持 Bug 的复现和修复。_
## 当前使用的 JumpServer 版本 (必填)
[在这里输入当前使用的 JumpServer 的版本号]
## 使用的版本类型 (必填)
- [ ] 社区版
- [ ] 企业版
- [ ] 企业试用版
**JumpServer 版本(v1.5.9以下不再支持)**
## 版本安装方式 (必填)
- [ ] 在线安装 (一键命令)
- [ ] 离线安装 (下载离线包)
- [ ] All-in-One
- [ ] 1Panel 安装
- [ ] Kubernetes 安装
- [ ] 源码安装
**浏览器版本**
## Bug 描述 (详细)
[在这里描述 Bug 的详细情况,包括其影响和出现的具体情况]
## 复现步骤
1. [描述如何复现 Bug 的第一步]
2. [描述如何复现 Bug 的第二步]
3. [以此类推,列出所有复现 Bug 所需的步骤]
**Bug 描述**
## 期望行为
[描述 Bug 出现时期望的系统行为或结果]
## 实际行为
[描述实际上发生了什么,以及 Bug 出现的具体情况]
## 系统环境
- 操作系统:[例如Windows 10, macOS Big Sur]
- 浏览器/应用版本:[如果适用,请提供相关版本信息]
- 其他相关环境信息:[如果有其他相关环境信息,请在此处提供]
## 附加信息(可选)
[在这里添加任何其他相关信息,如截图、错误信息等]
**Bug 重现步骤(有截图更好)**
1.
2.
3.

View File

@@ -1,50 +1,10 @@
---
name: 问题咨询
about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[Question] 问题标题"
title: "[Question] "
labels: 类型:提问
assignees:
- baijiangjie
assignees: wojiushixiaobai
---
## 注意
**请描述您的问题.** <br>
**JumpServer 版本( v2.28 之前的版本不再支持 )** <br>
_针对过于简单的 Bug 描述不予考虑。请确保提供足够的细节和信息以支持 Bug 的复现和修复。_
## 当前使用的 JumpServer 版本 (必填)
[在这里输入当前使用的 JumpServer 的版本号]
## 使用的版本类型 (必填)
- [ ] 社区版
- [ ] 企业版
- [ ] 企业试用版
## 版本安装方式 (必填)
- [ ] 在线安装 (一键命令)
- [ ] 离线安装 (下载离线包)
- [ ] All-in-One
- [ ] 1Panel 安装
- [ ] Kubernetes 安装
- [ ] 源码安装
## 问题描述 (详细)
[在这里描述你遇到的问题]
## 背景信息
- 操作系统:[例如Windows 10, macOS Big Sur]
- 浏览器/应用版本:[如果适用,请提供相关版本信息]
- 其他相关环境信息:[如果有其他相关环境信息,请在此处提供]
## 具体问题
[在这里详细描述你的问题,包括任何相关细节或错误信息]
## 尝试过的解决方法
[如果你已经尝试过解决问题,请在这里列出你已经尝试过的解决方法]
## 预期结果
[描述你期望的解决方案或结果]
## 我们的期望
[描述你希望我们提供的帮助或支持]
**请描述您的问题.**

View File

@@ -21,44 +21,17 @@ jobs:
actions: 'remove-labels'
labels: '状态:待反馈'
add-label-if-is-member:
add-label-if-not-author:
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:
- 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
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2
with:
actions: 'add-labels'
labels: '状态:待反馈'
- name: Remove require handle label
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2
with:
actions: 'remove-labels'

View File

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

View File

@@ -20,4 +20,4 @@ jobs:
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
with:
source-repo: 'git@github.com:jumpserver/jumpserver.git'
destination-repo: 'git@gitee.com:fit2cloud-feizhiyun/JumpServer.git'
destination-repo: 'git@gitee.com:jumpserver/jumpserver.git'

3
.gitignore vendored
View File

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

104
Dockerfile Normal file
View File

@@ -0,0 +1,104 @@
FROM python:3.9-slim as stage-build
ARG TARGETARCH
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
freetds-dev \
libpq-dev \
libffi-dev \
libjpeg-dev \
libldap2-dev \
libsasl2-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libaio-dev"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
locales \
openssh-client \
procps \
sshpass \
telnet \
unzip \
vim \
git \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \
&& echo "set mouse-=a" > ~/.vimrc \
&& echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/*
ARG DOWNLOAD_URL=https://download.jumpserver.org
RUN mkdir -p /opt/oracle/ \
&& cd /opt/oracle/ \
&& wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
&& unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
&& sh -c "echo /opt/oracle/instantclient_19_10 > /etc/ld.so.conf.d/oracle-instantclient.conf" \
&& ldconfig \
&& rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
WORKDIR /opt/jumpserver
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

View File

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

View File

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

96
Dockerfile.loong64 Normal file
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"]

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
a098bc6c06c9762b7d596d28865d5e5f1afe033e

View File

@@ -10,25 +10,10 @@
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
--------------------------
<p align="center">
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
------------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
- **SSH**: Linux / Unix / 网络设备 等;
- **Windows**: Web 方式连接 / 原生 RDP 连接;
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
- **NoSQL**: Redis / MongoDB 等;
- **GPT**: ChatGPT 等;
- **云服务**: Kubernetes / VMware vSphere 等;
- **Web 站点**: 各类系统的 Web 管理后台;
- **应用**: 通过 Remote App 连接各类应用。
## 产品特色
- **开源**: 零门槛,线上快速获取和安装;
@@ -37,10 +22,12 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
- **多云支持**: 一套系统,同时管理不同云上面的资产;
- **多租户**: 一套系统,多个子公司或部门同时使用;
- **云端存储**: 审计录像云端存储,永不丢失;
- **多应用支持**: 全面支持各类资产包括服务器、数据库、Windows RemoteApp、Kubernetes 等;
- **安全可靠**: 被广泛使用、验证和信赖,连续 9 年的持续研发投入和产品更新升级。
## UI 展示
![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png)
![UI展示](https://www.jumpserver.org/images/screenshot/1.png)
## 在线体验
@@ -54,14 +41,13 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
## 快速开始
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
- [手动安装](https://github.com/jumpserver/installer)
- [产品文档](https://docs.jumpserver.org)
- [在线学习](https://edu.fit2cloud.com/page/2635362)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
## 案例研究
- [腾讯音乐娱乐集团基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
- [腾讯海外游戏基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
- [万华化学通过JumpServer管理全球化分布式IT资产并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
- [雪花啤酒JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
@@ -75,33 +61,32 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信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 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core API 通信的组件项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
## 安全说明
@@ -111,9 +96,14 @@ JumpServer是一款安全产品请参考 [基本安全建议](https://docs.ju
- 邮箱support@fit2cloud.com
- 电话400-052-0755
## 致谢
- [Apache Guacamole](https://guacamole.apache.org/) Web 页面连接 RDP、SSH、VNC 等协议资产JumpServer Lion 组件使用到该项目;
- [OmniDB](https://omnidb.org/) Web 页面连接使用数据库JumpServer Web 数据库组件使用到该项目。
## License & Copyright
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved.
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at

View File

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

View File

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

View File

@@ -1,40 +1,35 @@
from django.shortcuts import get_object_or_404
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.status import HTTP_200_OK
from accounts import serializers
from accounts.filters import AccountFilterSet
from accounts.mixins import AccountRecordViewLogMixin
from accounts.models import Account
from assets.models import Asset, Node
from authentication.permissions import UserConfirmation, ConfirmType
from common.api.mixin import ExtraFilterFieldsMixin
from common.permissions import IsValidUser
from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission
__all__ = [
'AccountViewSet', 'AccountSecretsViewSet',
'AccountHistoriesSecretAPI', 'AssetAccountBulkCreateApi',
'AccountHistoriesSecretAPI'
]
class AccountViewSet(OrgBulkModelViewSet):
model = Account
search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment')
search_fields = ('username', 'asset__address', 'name')
filterset_class = AccountFilterSet
serializer_classes = {
'default': serializers.AccountSerializer,
'retrieve': serializers.AccountDetailSerializer,
}
rbac_perms = {
'partial_update': ['accounts.change_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')
def su_from_accounts(self, request, *args, **kwargs):
@@ -48,29 +43,25 @@ class AccountViewSet(OrgBulkModelViewSet):
asset = get_object_or_404(Asset, pk=asset_id)
accounts = asset.accounts.all()
else:
accounts = Account.objects.none()
accounts = []
accounts = self.filter_queryset(accounts)
serializer = serializers.AccountSerializer(accounts, many=True)
return Response(data=serializer.data)
@action(
methods=['post'], detail=False, url_path='username-suggestions',
permission_classes=[IsValidUser]
)
@action(methods=['get'], detail=False, url_path='username-suggestions')
def username_suggestions(self, request, *args, **kwargs):
asset_ids = request.data.get('assets', [])
node_ids = request.data.get('nodes', [])
username = request.data.get('username', '')
accounts = Account.objects.all()
if node_ids:
nodes = Node.objects.filter(id__in=node_ids)
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
asset_ids.extend(node_asset_ids)
asset_ids = request.query_params.get('assets')
node_keys = request.query_params.get('keys')
username = request.query_params.get('username')
assets = Asset.objects.all()
if asset_ids:
accounts = accounts.filter(asset_id__in=list(set(asset_ids)))
assets = assets.filter(id__in=asset_ids.split(','))
if node_keys:
patten = Node.get_node_all_children_key_pattern(node_keys.split(','))
assets = assets.filter(nodes__key__regex=patten)
accounts = Account.objects.filter(asset__in=assets)
if username:
accounts = accounts.filter(username__icontains=username)
usernames = list(accounts.values_list('username', flat=True).distinct()[:10])
@@ -80,14 +71,8 @@ class AccountViewSet(OrgBulkModelViewSet):
usernames = common + others
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(AccountRecordViewLogMixin, AccountViewSet):
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
"""
因为可能要导出所有账号,所以单独建立了一个 viewset
"""
@@ -102,21 +87,7 @@ class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
}
class AssetAccountBulkCreateApi(CreateAPIView):
serializer_class = serializers.AssetAccountBulkSerializer
rbac_perms = {
'POST': 'accounts.add_account',
}
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.create(serializer.validated_data)
serializer = serializers.AssetAccountBulkSerializerResultSerializer(data, many=True)
return Response(data=serializer.data, status=HTTP_200_OK)
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, ListAPIView):
class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
model = Account.history.model
serializer_class = serializers.AccountHistorySerializer
http_method_names = ['get', 'options']
@@ -128,19 +99,14 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixi
def get_object(self):
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):
account = self.get_object()
histories = account.history.all()
latest_history = account.history.first()
if not latest_history:
last_history = account.history.first()
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)
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

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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.response import Response
@@ -20,8 +20,8 @@ __all__ = [
class AutomationAssetsListApi(generics.ListAPIView):
model = BaseAutomation
serializer_class = serializers.AutomationAssetsSerializer
filterset_fields = ("name", "address")
search_fields = filterset_fields
filter_fields = ("name", "address")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
@@ -95,8 +95,8 @@ class AutomationExecutionViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
search_fields = ('trigger', 'automation__name')
filterset_fields = ('trigger', 'automation_id', 'automation__name')
search_fields = ('trigger',)
filterset_fields = ('trigger', 'automation_id')
serializer_class = serializers.AutomationExecutionSerializer
tp: str

View File

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

View File

@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.const import Source
from accounts.filters import GatheredAccountFilterSet
from accounts.models import GatherAccountsAutomation
from accounts.models import GatheredAccount
@@ -20,8 +22,8 @@ __all__ = [
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
model = GatherAccountsAutomation
filterset_fields = ('name',)
search_fields = filterset_fields
filter_fields = ('name',)
search_fields = filter_fields
serializer_class = serializers.GatherAccountAutomationSerializer
@@ -48,12 +50,22 @@ class GatheredAccountViewSet(OrgBulkModelViewSet):
'default': serializers.GatheredAccountSerializer,
}
rbac_perms = {
'sync_accounts': 'assets.add_gatheredaccount',
'sync_account': 'assets.add_gatheredaccount',
}
@action(methods=['post'], detail=False, url_path='sync-accounts')
def sync_accounts(self, request, *args, **kwargs):
gathered_account_ids = request.data.get('gathered_account_ids')
gathered_accounts = self.model.objects.filter(id__in=gathered_account_ids)
self.model.sync_accounts(gathered_accounts)
@action(methods=['post'], detail=True, url_path='sync')
def sync_account(self, request, *args, **kwargs):
gathered_account = super().get_object()
asset = gathered_account.asset
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)

View File

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

View File

@@ -6,5 +6,6 @@ class AccountsConfig(AppConfig):
name = 'accounts'
def ready(self):
from . import signal_handlers # noqa
from . import tasks # noqa
from . import signal_handlers
from . import tasks
__all__ = signal_handlers

View File

@@ -1,28 +1,26 @@
import os
import time
from openpyxl import Workbook
from collections import defaultdict, OrderedDict
from django.conf import settings
from django.db.models import F
from rest_framework import serializers
from xlsxwriter import Workbook
from accounts.const import AccountBackupType
from accounts.models.automations.backup_account import AccountBackupAutomation
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
from accounts.serializers import AccountSecretSerializer
from accounts.models import Account
from assets.const import AllTypes
from common.utils.file import encrypt_and_compress_zip_file, zip_files
from common.utils.timezone import local_now_filename, local_now_display
from terminal.models.component.storage import ReplayStorage
from accounts.serializers import AccountSecretSerializer
from accounts.notifications import AccountBackupExecutionTaskMsg
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')
class RecipientsNotFound(Exception):
pass
class BaseAccountHandler:
@classmethod
def unpack_data(cls, serializer_data, data=None):
@@ -74,26 +72,12 @@ class AssetAccountHandler(BaseAccountHandler):
@staticmethod
def get_filename(plan_name):
filename = os.path.join(
PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.xlsx'
PATH, f'{plan_name}-{local_now_display()}-{time.time()}.xlsx'
)
return filename
@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
def create_data_map(cls, accounts, section):
def create_data_map(cls, accounts):
data_map = defaultdict(list)
if not accounts.exists():
@@ -113,10 +97,9 @@ class AssetAccountHandler(BaseAccountHandler):
for tp, _accounts in account_type_map.items():
sheet_name = type_dict.get(tp, tp)
data = AccountSecretSerializer(_accounts, many=True).data
cls.handler_secret(data, section)
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
@@ -126,8 +109,8 @@ class AccountBackupHandler:
self.plan_name = self.execution.plan.name
self.is_frozen = False # 任务状态冻结标志
def create_excel(self, section='complete'):
print(
def create_excel(self):
logger.info(
'\n'
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
''
@@ -136,7 +119,7 @@ class AccountBackupHandler:
time_start = time.time()
files = []
accounts = self.execution.backup_accounts
data_map = AssetAccountHandler.create_data_map(accounts, section)
data_map = AssetAccountHandler.create_data_map(accounts)
if not data_map:
return files
@@ -144,23 +127,22 @@ class AccountBackupHandler:
wb = Workbook(filename)
for sheet, data in data_map.items():
ws = wb.add_worksheet(str(sheet))
for row_index, row_data in enumerate(data):
for col_index, col_data in enumerate(row_data):
ws.write_string(row_index, col_index, col_data)
wb.close()
ws = wb.create_sheet(str(sheet))
for row in data:
ws.append(row)
wb.save(filename)
files.append(filename)
timedelta = round((time.time() - time_start), 2)
print('创建备份文件完成: 用时 {}s'.format(timedelta))
logger.info('步骤完成: 用时 {}s'.format(timedelta))
return files
def send_backup_mail(self, files, recipients):
if not files:
return
recipients = User.objects.filter(id__in=list(recipients))
print(
logger.info(
'\n'
'\033[32m>>> 开始发送备份邮件\033[0m'
'\033[32m>>> 发送备份邮件\033[0m'
''
)
plan_name = self.plan_name
@@ -168,34 +150,12 @@ class AccountBackupHandler:
if not user.secret_key:
attachment_list = []
else:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
password = user.secret_key.encode('utf8')
attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, files)
attachment_list = [attachment, ]
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
print('邮件已发送至{}({})'.format(user, user.email))
for file in files:
os.remove(file)
def send_backup_obj_storage(self, files, recipients, password):
if not files:
return
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
print(
'\n'
'\033[32m>>> 开始发送备份文件到sftp服务器\033[0m'
''
)
plan_name = self.plan_name
for rec in recipients:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
if password:
print('\033[32m>>> 使用加密密码对文件进行加密中\033[0m')
encrypt_and_compress_zip_file(attachment, password, files)
else:
zip_files(attachment, files)
attachment_list = attachment
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
print('备份文件将发送至{}({})'.format(rec.name, rec.id))
logger.info('邮件已发送至{}({})'.format(user, user.email))
for file in files:
os.remove(file)
@@ -203,29 +163,33 @@ class AccountBackupHandler:
self.execution.reason = reason[:1024]
self.execution.is_success = is_success
self.execution.save()
print('\n已完成对任务状态的更新\n')
logger.info('已完成对任务状态的更新')
@staticmethod
def step_finished(is_success):
def step_finished(self, is_success):
if is_success:
print('任务执行成功')
logger.info('任务执行成功')
else:
print('任务执行失败')
logger.error('任务执行失败')
def _run(self):
is_success = False
error = '-'
try:
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email.value)
if backup_type == AccountBackupType.email.value:
self.backup_by_email()
elif backup_type == AccountBackupType.object_storage.value:
self.backup_by_obj_storage()
recipients = self.execution.plan_snapshot.get('recipients')
if not recipients:
logger.info(
'\n'
'\033[32m>>> 该备份任务未分配收件人\033[0m'
''
)
else:
files = self.create_excel()
self.send_backup_mail(files, recipients)
except Exception as e:
self.is_frozen = True
print('任务执行被异常中断')
print('下面打印发生异常的 Traceback 信息 : ')
print(e)
logger.error('任务执行被异常中断')
logger.info('下面打印发生异常的 Traceback 信息 : ')
logger.error(e, exc_info=True)
error = str(e)
else:
is_success = True
@@ -234,62 +198,16 @@ class AccountBackupHandler:
self.step_perform_task_update(is_success, reason)
self.step_finished(is_success)
def backup_by_obj_storage(self):
object_id = self.execution.snapshot.get('id')
zip_encrypt_password = AccountBackupAutomation.objects.get(id=object_id).zip_encrypt_password
obj_recipients_part_one = self.execution.snapshot.get('obj_recipients_part_one', [])
obj_recipients_part_two = self.execution.snapshot.get('obj_recipients_part_two', [])
if not obj_recipients_part_one and not obj_recipients_part_two:
print(
'\n'
'\033[31m>>> 该备份任务未分配sftp服务器\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
if obj_recipients_part_one and obj_recipients_part_two:
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\033[0m')
files = self.create_excel(section='front')
self.send_backup_obj_storage(files, obj_recipients_part_one, zip_encrypt_password)
files = self.create_excel(section='back')
self.send_backup_obj_storage(files, obj_recipients_part_two, zip_encrypt_password)
else:
recipients = obj_recipients_part_one or obj_recipients_part_two
files = self.create_excel()
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
def backup_by_email(self):
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
if not recipients_part_one and not recipients_part_two:
print(
'\n'
'\033[31m>>> 该备份任务未分配收件人\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
if recipients_part_one and recipients_part_two:
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\033[0m')
files = self.create_excel(section='front')
self.send_backup_mail(files, recipients_part_one)
files = self.create_excel(section='back')
self.send_backup_mail(files, recipients_part_two)
else:
recipients = recipients_part_one or recipients_part_two
files = self.create_excel()
self.send_backup_mail(files, recipients)
def run(self):
print('任务开始: {}'.format(local_now_display()))
logger.info('任务开始: {}'.format(local_now_display()))
time_start = time.time()
try:
self._run()
except Exception as e:
print('任务运行出现异常')
print('下面显示异常 Traceback 信息: ')
print(e)
logger.error('任务运行出现异常')
logger.error('下面显示异常 Traceback 信息: ')
logger.error(e, exc_info=True)
finally:
print('\n任务结束: {}'.format(local_now_display()))
logger.info('\n任务结束: {}'.format(local_now_display()))
timedelta = round((time.time() - time_start), 2)
print('用时: {}s'.format(timedelta))
logger.info('用时: {}'.format(timedelta))

View File

@@ -4,9 +4,13 @@ import time
from django.utils import timezone
from common.utils import get_logger
from common.utils.timezone import local_now_display
from .handlers import AccountBackupHandler
logger = get_logger(__name__)
class AccountBackupManager:
def __init__(self, execution):
@@ -19,7 +23,7 @@ class AccountBackupManager:
def do_run(self):
execution = self.execution
print('\n\033[33m# 账号备份计划正在执行\033[0m')
logger.info('\n\033[33m# 账号备份计划正在执行\033[0m')
handler = AccountBackupHandler(execution)
handler.run()
@@ -31,10 +35,10 @@ class AccountBackupManager:
self.time_end = time.time()
self.date_end = timezone.now()
print('\n\n' + '-' * 80)
print('计划执行结束 {}\n'.format(local_now_display()))
logger.info('\n\n' + '-' * 80)
logger.info('计划执行结束 {}\n'.format(local_now_display()))
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.save()

View File

@@ -1,61 +0,0 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
ansible_become: false
tasks:
- name: Test privileged account (paramiko)
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
register: ping_info
delegate_to: localhost
- name: Change asset password (paramiko)
custom_command:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
ignore_errors: true
when: ping_info is succeeded
register: change_info
delegate_to: localhost
- name: Verify password (paramiko)
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
delegate_to: localhost

View File

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

View File

@@ -1,7 +1,7 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Test MongoDB connection
@@ -11,9 +11,9 @@
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 | default('') }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
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
@@ -31,15 +31,15 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
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
register: change_info
- name: Verify password
mongodb_ping:
@@ -49,7 +49,10 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
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}}"
when:
- db_info is succeeded
- change_info is succeeded

View File

@@ -1,12 +1,6 @@
id: change_secret_mongodb
name: "{{ 'MongoDB account change secret' | trans }}"
name: Change secret for MongoDB
category: database
type:
- mongodb
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

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

View File

@@ -1,13 +1,7 @@
id: change_secret_mysql
name: "{{ 'MySQL account change secret' | trans }}"
name: Change secret for MySQL
category: database
type:
- mysql
- mariadb
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

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

View File

@@ -1,11 +1,6 @@
id: change_secret_oracle
name: "{{ 'Oracle account change secret' | trans }}"
name: Change secret for Oracle
category: database
type:
- oracle
method: change_secret
i18n:
Oracle account change secret:
zh: Oracle 账号改密
ja: Oracle アカウントのパスワード変更

View File

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

View File

@@ -1,11 +1,6 @@
id: change_secret_postgresql
name: "{{ 'PostgreSQL account change secret' | trans }}"
name: Change secret for PostgreSQL
category: database
type:
- postgresql
method: change_secret
i18n:
PostgreSQL account change secret:
zh: PostgreSQL 账号改密
ja: PostgreSQL アカウントのパスワード変更

View File

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

View File

@@ -1,11 +1,6 @@
id: change_secret_sqlserver
name: "{{ 'SQLServer account change secret' | trans }}"
name: Change secret for SQLServer
category: database
type:
- sqlserver
method: change_secret
i18n:
SQLServer account change secret:
zh: SQLServer 账号改密
ja: SQLServer アカウントのパスワード変更

View File

@@ -1,101 +1,54 @@
- hosts: demo
gather_facts: no
tasks:
- name: "Test privileged {{ jms_account.username }} account"
- name: Test privileged 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"
- name: Change password
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
when: 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
ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
dest: "{{ kwargs.dest }}"
regexp: "{{ kwargs.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- secret_type == "ssh_key"
- kwargs.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key"
- name: Change 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
exclusive: "{{ kwargs.exclusive }}"
when: secret_type == "ssh_key"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
when: secret_type == "password"
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
when: secret_type == "ssh_key"

View File

@@ -1,61 +1,6 @@
id: change_secret_aix
name: "{{ 'AIX account change secret' | trans }}"
name: Change secret for aix
category: host
type:
- AIX
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,101 +1,54 @@
- hosts: demo
gather_facts: no
tasks:
- name: "Test privileged {{ jms_account.username }} account"
- name: Test privileged 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"
- name: Change password
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
when: 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
ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
dest: "{{ kwargs.dest }}"
regexp: "{{ kwargs.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- secret_type == "ssh_key"
- kwargs.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key"
- name: Change 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
exclusive: "{{ kwargs.exclusive }}"
when: secret_type == "ssh_key"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
when: secret_type == "password"
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
when: secret_type == "ssh_key"

View File

@@ -1,63 +1,7 @@
id: change_secret_posix
name: "{{ 'Posix account change secret' | trans }}"
name: Change secret for posix
category: host
type:
- unix
- linux
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:
# 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
ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
password_never_expires: yes
groups: "{{ params.groups }}"
groups: "{{ user_info.groups[0].name }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection

View File

@@ -1,26 +1,7 @@
id: change_secret_local_windows
name: "{{ 'Windows account change secret' | trans }}"
name: Change secret local account for Windows
version: 1
method: change_secret
category: host
type:
- windows
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account 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

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

View File

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

View File

@@ -1,20 +1,20 @@
import os
import time
from collections import defaultdict
from copy import deepcopy
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook
from openpyxl import Workbook
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy, ChangeSecretRecordStatusChoice
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretFailedMsg
from accounts.notifications import ChangeSecretExecutionTaskMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import HostTypes
from common.utils import get_logger
from common.utils import get_logger, lazyproperty
from common.utils.file import encrypt_and_compress_zip_file
from common.utils.timezone import local_now_filename
from common.utils.timezone import local_now_display
from users.models import User
from ..base.manager import AccountBasePlaybookManager
from ...utils import SecretGenerator
@@ -27,63 +27,45 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.record_map = self.execution.snapshot.get('record_map', {})
self.secret_type = self.execution.snapshot.get('secret_type')
self.method_hosts_mapper = defaultdict(list)
self.secret_type = self.execution.snapshot['secret_type']
self.secret_strategy = self.execution.snapshot.get(
'secret_strategy', SecretStrategy.custom
)
self.ssh_key_change_strategy = self.execution.snapshot.get(
'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 = {} # 做个映射,方便后面处理
@classmethod
def method_type(cls):
return AutomationTypes.change_secret
def get_ssh_params(self, account, secret, secret_type):
def get_kwargs(self, account, secret):
kwargs = {}
if secret_type != SecretType.SSH_KEY:
if self.secret_type != SecretType.SSH_KEY:
return kwargs
kwargs['strategy'] = self.ssh_key_change_strategy
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
username = account.username
path = f'/{username}' if username == "root" else f'/home/{username}'
kwargs['dest'] = f'{path}/.ssh/authorized_keys'
kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
return kwargs
def secret_generator(self, secret_type):
@lazyproperty
def secret_generator(self):
return SecretGenerator(
self.secret_strategy, secret_type,
self.secret_strategy, self.secret_type,
self.execution.snapshot.get('password_rules')
)
def get_secret(self, secret_type):
def get_secret(self):
if self.secret_strategy == SecretStrategy.custom:
return self.execution.snapshot['secret']
else:
return self.secret_generator(secret_type).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
return self.secret_generator.get_secret()
def host_callback(
self, host, asset=None, account=None,
@@ -96,67 +78,61 @@ class ChangeSecretManager(AccountBasePlaybookManager):
if host.get('error'):
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:
print('没有发现待处理的账号: %s 用户ID: %s 类型: %s' % (
asset.name, self.account_ids, self.secret_type
print('没有发现待改密账号: %s 用户: %s 类型: %s' % (
asset.name, self.snapshot_account_usernames, self.secret_type
))
return []
records = []
method_attr = getattr(automation, self.method_type() + '_method')
method_hosts = self.method_hosts_mapper[method_attr]
method_hosts = [h for h in method_hosts if h != host['name']]
inventory_hosts = []
records = []
host['secret_type'] = self.secret_type
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
host['ssh_params'] = {}
for account in accounts:
h = deepcopy(host)
secret_type = account.secret_type
h['name'] += '(' + account.username + ')'
if self.secret_type is None:
new_secret = account.secret
else:
new_secret = self.get_secret(secret_type)
if new_secret is None:
print(f'new_secret is None, account: {account}')
continue
asset_account_id = f'{asset.id}-{account.id}'
if asset_account_id not in self.record_map:
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
)
records.append(recorder)
else:
record_id = self.record_map[asset_account_id]
try:
recorder = ChangeSecretRecord.objects.get(id=record_id)
except ChangeSecretRecord.DoesNotExist:
print(f"Record {record_id} not found")
continue
new_secret = self.get_secret()
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
)
records.append(recorder)
self.name_recorder_mapper[h['name']] = recorder
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)
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'] = {
'name': account.name,
'username': account.username,
'secret_type': secret_type,
'secret': account.escape_jinja2_syntax(new_secret),
'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
'secret_type': account.secret_type,
'secret': new_secret,
'private_key_path': private_key_path
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None
inventory_hosts.append(h)
method_hosts.append(h['name'])
self.method_hosts_mapper[method_attr] = method_hosts
ChangeSecretRecord.objects.bulk_create(records)
return inventory_hosts
@@ -164,46 +140,27 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.status = 'success'
recorder.date_finished = timezone.now()
recorder.save()
account = recorder.account
if not account:
print("Account not found, deleted ?")
return
account.secret = recorder.new_secret
account.date_updated = timezone.now()
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
recorder.save()
account.save(update_fields=['secret', 'version', 'date_updated'])
break
except Exception as e:
retry_count += 1
if retry_count == max_retries:
self.on_host_error(host, str(e), result)
else:
print(f'retry {retry_count} times for {host} recorder save error: {e}')
time.sleep(1)
account.save(update_fields=['secret'])
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.status = 'failed'
recorder.date_finished = timezone.now()
recorder.error = error
try:
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
recorder.save()
def on_runner_failed(self, runner, e):
logger.error("Account error: ", e)
logger.error("Change secret error: ", e)
def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \
@@ -212,69 +169,35 @@ class ChangeSecretManager(AccountBasePlaybookManager):
return False
return True
@staticmethod
def get_summary(recorders):
total, succeed, failed = 0, 0, 0
for recorder in recorders:
if recorder.status == ChangeSecretRecordStatusChoice.success.value:
succeed += 1
else:
failed += 1
total += 1
summary = _('Success: %s, Failed: %s, Total: %s') % (succeed, failed, total)
return summary
def run(self, *args, **kwargs):
if self.secret_type and not self.check_secret():
if not self.check_secret():
return
super().run(*args, **kwargs)
recorders = list(self.name_recorder_mapper.values())
summary = self.get_summary(recorders)
print(summary, end='')
if self.record_map:
return
failed_recorders = [
r for r in recorders
if r.status == ChangeSecretRecordStatusChoice.failed.value
]
recorders = self.name_recorder_mapper.values()
recorders = list(recorders)
self.send_recorder_mail(recorders)
def send_recorder_mail(self, recorders):
recipients = self.execution.recipients
if not recorders or not recipients:
return
recipients = User.objects.filter(id__in=list(recipients.keys()))
if not recipients:
return
if failed_recorders:
name = self.execution.snapshot.get('name')
execution_id = str(self.execution.id)
_ids = [r.id for r in failed_recorders]
asset_account_errors = ChangeSecretRecord.objects.filter(
id__in=_ids).values_list('asset__name', 'account__username', 'error')
for user in recipients:
ChangeSecretFailedMsg(name, execution_id, user, asset_account_errors).publish()
if not recorders:
return
self.send_recorder_mail(recipients, recorders, summary)
def send_recorder_mail(self, recipients, recorders, summary):
name = self.execution.snapshot['name']
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')
filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx')
if not self.create_file(recorders, filename):
return
for user in recipients:
attachments = []
if user.secret_key:
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, user.secret_key, [filename])
password = user.secret_key.encode('utf8')
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, [filename])
attachments = [attachment]
ChangeSecretExecutionTaskMsg(name, user, summary).publish(attachments)
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
os.remove(filename)
@staticmethod
@@ -289,9 +212,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
rows.insert(0, header)
wb = Workbook(filename)
ws = wb.add_worksheet('Sheet1')
for row_index, row_data in enumerate(rows):
for col_index, col_data in enumerate(row_data):
ws.write_string(row_index, col_index, col_data)
wb.close()
ws = wb.create_sheet('Sheet1')
for row in rows:
ws.append(row)
wb.save(filename)
return True

View File

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

View File

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

View File

@@ -1,11 +1,6 @@
id: gather_accounts_mongodb
name: "{{ 'MongoDB account gather' | trans }}"
name: Gather account from MongoDB
category: database
type:
- mongodb
method: gather_accounts
i18n:
MongoDB account gather:
zh: MongoDB 账号收集
ja: MongoDB アカウントの収集

View File

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

View File

@@ -1,12 +1,7 @@
id: gather_accounts_mysql
name: "{{ 'MySQL account gather' | trans }}"
name: Gather account from MySQL
category: database
type:
- mysql
- mariadb
method: gather_accounts
i18n:
MySQL account gather:
zh: MySQL 账号收集
ja: MySQL アカウントの収集

View File

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

View File

@@ -1,11 +1,6 @@
id: gather_accounts_oracle
name: "{{ 'Oracle account gather' | trans }}"
name: Gather account from Oracle
category: database
type:
- oracle
method: gather_accounts
i18n:
Oracle account gather:
zh: Oracle 账号收集
ja: Oracle アカウントの収集

View File

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

View File

@@ -1,11 +1,6 @@
id: gather_accounts_postgresql
name: "{{ 'PostgreSQL account gather' | trans }}"
name: Gather account for PostgreSQL
category: database
type:
- postgresql
method: gather_accounts
i18n:
PostgreSQL account gather:
zh: PostgreSQL 账号收集
ja: PostgreSQL アカウントの収集

View File

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

View File

@@ -5,7 +5,7 @@
ansible.builtin.shell:
cmd: >
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
echo $k
else

View File

@@ -1,13 +1,7 @@
id: gather_accounts_posix
name: "{{ 'Posix account gather' | trans }}"
name: Gather posix account
category: host
type:
- linux
- unix
method: gather_accounts
i18n:
Posix account gather:
zh: 使用命令 getent passwd 收集 Posix 资产账号
ja: コマンド getent を使用してアセットアカウントを収集する
en: Using command getent to gather accounts

View File

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

View File

@@ -1,13 +1,7 @@
id: gather_accounts_windows
name: "{{ 'Windows account gather' | trans }}"
name: Gather account windows
version: 1
method: gather_accounts
category: host
type:
- windows
i18n:
Windows account gather:
zh: 使用命令 net user 收集 Windows 账号
ja: コマンド net user を使用して Windows アカウントを収集する
en: Using command net user to gather accounts

View File

@@ -1,14 +1,9 @@
from collections import defaultdict
from accounts.const import AutomationTypes
from accounts.models import GatheredAccount
from assets.models import Asset
from common.utils import get_logger
from orgs.utils import tmp_to_org
from users.models import User
from .filter import GatherAccountsFilter
from ..base.manager import AccountBasePlaybookManager
from ...notifications import GatherAccountChangeMsg
logger = get_logger(__name__)
@@ -17,10 +12,6 @@ class GatherAccountsManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_asset_mapper = {}
self.asset_account_info = {}
self.asset_username_mapper = defaultdict(set)
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
@classmethod
def method_type(cls):
@@ -31,109 +22,29 @@ class GatherAccountsManager(AccountBasePlaybookManager):
self.host_asset_mapper[host['name']] = asset
return host
def filter_success_result(self, tp, result):
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
def filter_success_result(self, host, result):
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
return result
def generate_data(self, asset, result):
data = []
for username, info in result.items():
self.asset_username_mapper[str(asset.id)].add(username)
d = {'asset': asset, 'username': username, 'present': True}
if info.get('date'):
d['date_last_login'] = info['date']
if info.get('address'):
d['address_last_login'] = info['address'][:32]
data.append(d)
return data
def collect_asset_account_info(self, asset, result):
data = self.generate_data(asset, result)
self.asset_account_info[asset] = data
@staticmethod
def get_nested_info(data, *keys):
for key in keys:
data = data.get(key, {})
if not data:
break
return data
def update_or_create_gathered_accounts(asset, result):
with tmp_to_org(asset.org_id):
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
for username, data in result.items():
d = {'asset': asset, 'username': username, 'present': True}
if data.get('date'):
d['date_last_login'] = data['date']
if data.get('address'):
d['address_last_login'] = data['address'][:32]
GatheredAccount.objects.update_or_create(
defaults=d, asset=asset, username=username,
)
def on_host_success(self, host, result):
info = self.get_nested_info(result, 'debug', 'res', 'info')
info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host)
if asset and info:
result = self.filter_success_result(asset.type, info)
self.collect_asset_account_info(asset, result)
self.update_or_create_gathered_accounts(asset, result)
else:
print(f'\033[31m Not found {host} info \033[0m\n')
def update_or_create_accounts(self):
for asset, data in self.asset_account_info.items():
with tmp_to_org(asset.org_id):
gathered_accounts = []
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
for d in data:
username = d['username']
gathered_account, __ = GatheredAccount.objects.update_or_create(
defaults=d, asset=asset, username=username,
)
gathered_accounts.append(gathered_account)
if not self.is_sync_account:
continue
GatheredAccount.sync_accounts(gathered_accounts)
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
users, change_info = self.generate_send_users_and_change_info()
self.update_or_create_accounts()
self.send_email_if_need(users, change_info)
def generate_send_users_and_change_info(self):
recipients = self.execution.recipients
if not self.asset_username_mapper or not recipients:
return None, None
users = User.objects.filter(id__in=recipients)
if not users:
return users, None
asset_ids = self.asset_username_mapper.keys()
assets = Asset.objects.filter(id__in=asset_ids)
gather_accounts = GatheredAccount.objects.filter(asset_id__in=asset_ids, present=True)
asset_id_map = {str(asset.id): asset for asset in assets}
asset_id_username = list(assets.values_list('id', 'accounts__username'))
asset_id_username.extend(list(gather_accounts.values_list('asset_id', 'username')))
system_asset_username_mapper = defaultdict(set)
for asset_id, username in asset_id_username:
system_asset_username_mapper[str(asset_id)].add(username)
change_info = {}
for asset_id, usernames in self.asset_username_mapper.items():
system_usernames = system_asset_username_mapper.get(asset_id)
if not system_usernames:
continue
add_usernames = usernames - system_usernames
remove_usernames = system_usernames - usernames
k = f'{asset_id_map[asset_id]}[{asset_id}]'
if not add_usernames and not remove_usernames:
continue
change_info[k] = {
'add_usernames': ', '.join(add_usernames),
'remove_usernames': ', '.join(remove_usernames),
}
return users, change_info
@staticmethod
def send_email_if_need(users, change_info):
if not users or not change_info:
return
for user in users:
GatherAccountChangeMsg(user, change_info).publish_async()
logger.error("Not found info".format(host))

View File

@@ -1,6 +1,30 @@
import os
import copy
from accounts.const import AutomationTypes
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__))
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: /opt/py3/bin/python
tasks:
- name: Test MongoDB connection
mongodb_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info
- name: Display MongoDB version
debug:
var: db_info.server_version
when: db_info is succeeded
- name: Change MongoDB password
mongodb_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded
- name: Verify password
mongodb_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"

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,53 +0,0 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks:
- name: Test MySQL connection
community.mysql.mysql_info:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version
register: db_info
- name: MySQL version
debug:
var: db_info.version.full
- name: Change MySQL password
community.mysql.mysql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded
- name: Verify password
community.mysql.mysql_info:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version

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,42 +0,0 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test Oracle connection
oracle_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
register: db_info
- name: Display Oracle version
debug:
var: db_info.server_version
when: db_info is succeeded
- name: Change Oracle password
oracle_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded
- name: Verify password
oracle_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}"

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,47 +0,0 @@
- hosts: postgre
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test PostgreSQL connection
community.postgresql.postgresql_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
register: result
failed_when: not result.is_available
- name: Display PostgreSQL version
debug:
var: result.server_version.full
when: result is succeeded
- name: Change PostgreSQL password
community.postgresql.postgresql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
register: change_info
- name: Verify password
community.postgresql.postgresql_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
when:
- result is succeeded
- change_info is succeeded
register: result
failed_when: not result.is_available

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: /opt/py3/bin/python
tasks:
- name: Test SQLServer connection
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
register: db_info
- name: SQLServer version
set_fact:
info:
version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}"
- debug:
var: info
- name: Check whether SQLServer User exist
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
when: db_info is succeeded
register: user_exist
- name: Change SQLServer password
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
register: change_info
- name: Add SQLServer user
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
register: change_info
- name: Verify password
community.general.mssql_script:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version

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

View File

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

View File

@@ -1,102 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: "Test privileged {{ jms_account.username }} account"
ansible.builtin.ping:
- name: "Check if {{ account.username }} user exists"
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

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

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

View File

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

View File

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

View File

@@ -1,4 +1,10 @@
from accounts.const import AutomationTypes
from copy import deepcopy
from django.db.models import QuerySet
from accounts.const import AutomationTypes, SecretType
from accounts.models import Account
from assets.const import HostTypes
from common.utils import get_logger
from ..base.manager import AccountBasePlaybookManager
from ..change_secret.manager import ChangeSecretManager
@@ -7,11 +13,109 @@ logger = get_logger(__name__)
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
ansible_account_prefer = ''
@classmethod
def method_type(cls):
return AutomationTypes.push_account
def create_nonlocal_accounts(self, accounts, snapshot_account_usernames, asset):
secret_type = self.secret_type
usernames = accounts.filter(secret_type=secret_type).values_list(
'username', flat=True
)
create_usernames = set(snapshot_account_usernames) - set(usernames)
create_account_objs = [
Account(
name=f'{username}-{secret_type}', username=username,
secret_type=secret_type, asset=asset,
)
for username in create_usernames
]
Account.objects.bulk_create(create_account_objs)
def get_accounts(self, privilege_account, accounts: QuerySet):
if not privilege_account:
print(f'not privilege account')
return []
snapshot_account_usernames = self.execution.snapshot['accounts']
if '*' in snapshot_account_usernames:
return accounts.exclude(username=privilege_account.username)
asset = privilege_account.asset
self.create_nonlocal_accounts(accounts, snapshot_account_usernames, asset)
accounts = asset.accounts.exclude(username=privilege_account.username).filter(
username__in=snapshot_account_usernames, secret_type=self.secret_type
)
return accounts
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super(ChangeSecretManager, self).host_callback(
host, asset=asset, account=account, automation=automation,
path_dir=path_dir, **kwargs
)
if host.get('error'):
return host
accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = []
host['secret_type'] = self.secret_type
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
msg = f'Windows {asset} does not support ssh key push \n'
print(msg)
return inventory_hosts
for account in accounts:
h = deepcopy(host)
h['name'] += '(' + account.username + ')'
new_secret = self.get_secret()
self.name_recorder_mapper[h['name']] = {
'account': account, 'new_secret': new_secret,
}
private_key_path = None
if self.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(new_secret, path_dir)
new_secret = self.generate_public_key(new_secret)
h['kwargs'] = self.get_kwargs(account, new_secret)
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': account.secret_type,
'secret': new_secret,
'private_key_path': private_key_path
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
account_info = self.name_recorder_mapper.get(host)
if not account_info:
return
account = account_info['account']
new_secret = account_info['new_secret']
if not account:
return
account.secret = new_secret
account.save(update_fields=['secret'])
def on_host_error(self, host, error, result):
pass
def on_runner_failed(self, runner, e):
logger.error("Pust account error: ", e)
def run(self, *args, **kwargs):
if not self.check_secret():
return
super().run(*args, **kwargs)
# @classmethod
# def trigger_by_asset_create(cls, asset):
# automations = PushAccountAutomation.objects.filter(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,26 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: "Get user home directory path"
ansible.builtin.shell:
cmd: "getent passwd {{ account.username }} | cut -d: -f6"
register: user_home_dir
ignore_errors: yes
- name: "Check if user home directory exists"
ansible.builtin.stat:
path: "{{ user_home_dir.stdout }}"
register: home_dir
when: user_home_dir.stdout != ""
- name: "Rename user home directory if it exists"
ansible.builtin.command:
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
- name: "Remove account"
ansible.builtin.user:
name: "{{ account.username }}"
state: absent
remove: "{{ home_dir.stat.exists }}"
when: home_dir.stat | default(false)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +0,0 @@
- hosts: custom
gather_facts: no
vars:
ansible_shell_type: sh
ansible_connection: local
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Verify account (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"

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