Compare commits

..

26 Commits
v4.4.1 ... v4.0

Author SHA1 Message Date
fit2bot
c8eb7cccb1 perf: Modify error message for desktop client login 2024-07-31 10:58:08 +08:00
wangruidong
001828fb2b fix: stop job failed 2024-07-30 18:49:17 +08:00
feng
d0e355a86d perf: When account push change secret windows only modify the type equal to password 2024-07-30 18:33:19 +08:00
wangruidong
4a5f327498 fix: Arbitrary File Read in Ansible Play 2024-07-30 18:15:37 +08:00
Eric
04761a9373 perf: Check if CORE_HOST should ignore SSL 2024-07-30 16:55:04 +08:00
feng
077731a4b4 perf: Saml2 callback url miss port 2024-07-26 18:14:04 +08:00
feng
75e79efa5f perf: The locked IP shows the username + translate 2024-07-17 18:10:42 +08:00
fit2bot
ca5ffb6278 fix: session viewset api permission validation (#13749)
* fix: session viewset api permission validation

* fix: some api permission validation

---------

Co-authored-by: Bai <baijiangjie@gmail.com>
2024-07-17 15:36:10 +08:00
wangruidong
c7e149f15d fix: 在线同步获取不到资产问题 2024-07-17 15:35:59 +08:00
feng
60ac5ddaf2 perf: Dashboard user login statistics issue 2024-07-17 14:57:30 +08:00
feng
244407b212 perf: Translate 2024-07-17 11:37:42 +08:00
Bai
f08f01fe49 fix: authentication passkey model migrations 2024-07-17 10:41:14 +08:00
fit2bot
b2f11bbbf3 perf: Migrate (#13742)
Co-authored-by: feng <1304903146@qq.com>
2024-07-17 10:24:19 +08:00
Bai
6108a09089 fix: i18n error 2024-07-17 10:00:50 +08:00
Bai
93cf58ca76 fix: i18n error 2024-07-17 09:53:39 +08:00
feng
587c49082f perf: Notification remove kael magnus 2024-07-16 19:33:50 +08:00
fit2bot
50090c5310 perf: Translate (#13732)
Co-authored-by: feng <1304903146@qq.com>
2024-07-16 19:02:49 +08:00
吴小白
b4182a0d1c fix: deploy applet host 2024-07-16 12:49:04 +08:00
wangruidong
8a2d7b69fa perf: 社区版移除magnus 2024-07-16 10:41:45 +08:00
fit2bot
84821e97f4 perf: Community supports custom platforms (#13720)
Co-authored-by: feng <1304903146@qq.com>
2024-07-15 17:32:03 +08:00
fit2bot
e0f20ce047 perf: Translate (#13717)
Co-authored-by: feng <1304903146@qq.com>
2024-07-15 17:06:30 +08:00
ibuler
375f60fef8 perf: community add postgre support 2024-07-15 16:19:03 +08:00
fit2bot
ced79ea6c9 perf: ConnectionTokenSerializer add org_name label (#13714)
Co-authored-by: feng <1304903146@qq.com>
2024-07-15 14:33:40 +08:00
吴小白
2d4b9ff5a8 perf: remove receptor 2024-07-12 18:38:45 +08:00
wangruidong
0a5467886a fix: creat linux asset protocol default value is sftp 2024-07-09 19:04:31 +08:00
吴小白
9809415f78 fix: FromAsCasing keywords 2024-07-09 10:36:18 +08:00
422 changed files with 9924 additions and 18735 deletions

View File

@@ -8,4 +8,3 @@ celerybeat.pid
.vagrant/
apps/xpack/.git
.history/
.idea

View File

@@ -1,72 +0,0 @@
name: Build and Push Base Image
on:
pull_request:
branches:
- 'dev'
- 'v*'
paths:
- poetry.lock
- pyproject.toml
- Dockerfile-base
- package.json
- go.mod
- yarn.lock
- pom.xml
- install_deps.sh
- utils/clean_site_packages.sh
types:
- opened
- synchronize
- reopened
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract date
id: vars
run: echo "IMAGE_TAG=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV
- name: Extract repository name
id: repo
run: echo "REPO=$(basename ${{ github.repository }})" >> $GITHUB_ENV
- name: Build and push multi-arch image
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
file: Dockerfile-base
tags: jumpserver/core-base:${{ env.IMAGE_TAG }}
- name: Update Dockerfile
run: |
sed -i 's|-base:.* AS stage-build|-base:${{ env.IMAGE_TAG }} AS stage-build|' Dockerfile
- name: Commit changes
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add Dockerfile
git commit -m "perf: Update Dockerfile with new base image tag"
git push origin ${{ github.event.pull_request.head.ref }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,31 +0,0 @@
name: Check I18n files CompileMessages
on:
pull_request:
branches:
- 'dev'
paths:
- 'apps/i18n/core/**/*.po'
types:
- opened
- synchronize
- reopened
jobs:
compile-messages-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and check compilemessages
uses: docker/build-push-action@v6
with:
platforms: linux/amd64
push: false
file: Dockerfile
target: stage-build
tags: jumpserver/core:stage-build

View File

@@ -1,24 +0,0 @@
name: Publish Release to Discord
on:
release:
types: [published]
jobs:
send_discord_notification:
runs-on: ubuntu-latest
if: startsWith(github.event.release.tag_name, 'v4.')
steps:
- name: Send release notification to Discord
env:
WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK }}
run: |
# 获取标签名称和 release body
TAG_NAME="${{ github.event.release.tag_name }}"
RELEASE_BODY="${{ github.event.release.body }}"
# 使用 jq 构建 JSON 数据,以确保安全传递
JSON_PAYLOAD=$(jq -n --arg tag "# JumpServer $TAG_NAME Released! 🚀" --arg body "$RELEASE_BODY" '{content: "\($tag)\n\($body)"}')
# 使用 curl 发送 JSON 数据
curl -X POST -H "Content-Type: application/json" -d "$JSON_PAYLOAD" "$WEBHOOK_URL"

View File

@@ -1,28 +0,0 @@
name: LLM Code Review
permissions:
contents: read
pull-requests: write
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
llm-code-review:
runs-on: ubuntu-latest
steps:
- uses: fit2cloud/LLM-CodeReview-Action@main
env:
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
LANGUAGE: English
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
MODEL: qwen2-1.5b-instruct
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
top_p: 1
temperature: 1
# max_tokens: 10000
MAX_PATCH_LENGTH: 10000
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"

View File

@@ -1,4 +1,3 @@
[settings]
line_length=120
known_first_party=common,users,assets,perms,authentication,jumpserver,notification,ops,orgs,rbac,settings,terminal,tickets

View File

@@ -1,25 +1,93 @@
FROM jumpserver/core-base:20241105_025649 AS stage-build
FROM debian:bullseye-slim AS stage-1
ARG TARGETARCH
ARG DEPENDENCIES=" \
ca-certificates \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt
ARG CHECK_VERSION=v1.0.2
RUN set -ex \
&& wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& mv check /usr/local/bin/ \
&& chown root:root /usr/local/bin/check \
&& chmod 755 /usr/local/bin/check \
&& rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz
ARG VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN echo > /opt/jumpserver/config.yml \
&& \
if [ -n "${VERSION}" ]; then \
sed -i "s@VERSION = .*@VERSION = '${VERSION}'@g" apps/jumpserver/const.py; \
fi
FROM python:3.11-slim-bullseye AS stage-2
ARG TARGETARCH
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
default-libmysqlclient-dev \
freetds-dev \
gettext \
libkrb5-dev \
libldap2-dev \
libsasl2-dev"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
RUN --mount=type=cache,target=/root/.cache,sharing=locked,id=core \
--mount=type=bind,source=poetry.lock,target=poetry.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
&& poetry install --only main
COPY --from=stage-1 /opt/jumpserver /opt/jumpserver
RUN set -ex \
&& export SECRET_KEY=$(head -c100 < /dev/urandom | base64 | tr -dc A-Za-z0-9 | head -c 48) \
&& . /opt/py3/bin/activate \
&& cd apps \
&& python manage.py compilemessages
FROM python:3.11-slim-bullseye
ARG TARGETARCH
ENV LANG=en_US.UTF-8 \
PATH=/opt/py3/bin:$PATH
@@ -28,35 +96,38 @@ ARG DEPENDENCIES=" \
libx11-dev"
ARG TOOLS=" \
cron \
ca-certificates \
default-libmysqlclient-dev \
openssh-client \
sshpass \
bubblewrap"
ARG APT_MIRROR=http://deb.debian.org
RUN set -ex \
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update > /dev/null \
&& 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 \
&& apt-get clean all \
&& rm -rf /var/lib/apt/lists/* \
&& echo "0 3 * * * root find /tmp -type f -mtime +1 -size +1M -exec rm -f {} \; && date > /tmp/clean.log" > /etc/cron.d/cleanup_tmp \
&& chmod 0644 /etc/cron.d/cleanup_tmp
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc
COPY --from=stage-build /opt /opt
COPY --from=stage-build /usr/local/bin /usr/local/bin
COPY --from=stage-build /opt/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
COPY --from=stage-2 /opt /opt
COPY --from=stage-1 /usr/local/bin /usr/local/bin
COPY --from=stage-1 /opt/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
WORKDIR /opt/jumpserver
ARG VERSION
ENV VERSION=$VERSION
VOLUME /opt/jumpserver/data
ENTRYPOINT ["./entrypoint.sh"]

View File

@@ -1,60 +0,0 @@
FROM python:3.11-slim-bullseye
ARG TARGETARCH
# Install APT dependencies
ARG DEPENDENCIES=" \
ca-certificates \
wget \
g++ \
make \
pkg-config \
default-libmysqlclient-dev \
freetds-dev \
gettext \
libkrb5-dev \
libldap2-dev \
libsasl2-dev"
ARG APT_MIRROR=http://deb.debian.org
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update > /dev/null \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
# Install bin tools
ARG CHECK_VERSION=v1.0.4
RUN set -ex \
&& wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& mv check /usr/local/bin/ \
&& chown root:root /usr/local/bin/check \
&& chmod 755 /usr/local/bin/check \
&& rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz
# Install Python dependencies
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.org/simple
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
ENV ANSIBLE_COLLECTIONS_PATHS=/opt/py3/lib/python3.11/site-packages/ansible_collections
RUN --mount=type=cache,target=/root/.cache \
--mount=type=bind,source=poetry.lock,target=poetry.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
--mount=type=bind,source=utils/clean_site_packages.sh,target=clean_site_packages.sh \
--mount=type=bind,source=requirements/collections.yml,target=collections.yml \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
&& . /opt/py3/bin/activate \
&& poetry config virtualenvs.create false \
&& poetry install --no-cache --only main \
&& ansible-galaxy collection install -r collections.yml --force --ignore-certs \
&& bash clean_site_packages.sh \
&& poetry cache clear pypi --all

View File

@@ -1,12 +1,38 @@
ARG VERSION=dev
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} AS build-xpack
FROM jumpserver/core:${VERSION}-ce
FROM python:3.11-slim-bullseye AS build-core
ARG BUILD_DEPENDENCIES=" \
g++"
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
RUN --mount=type=cache,target=/root/.cache,sharing=locked,id=core \
--mount=type=bind,source=poetry.lock,target=/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 --only xpack
FROM registry.fit2cloud.com/jumpserver/core:${VERSION}-ce
ARG TARGETARCH
ARG TOOLS=" \
g++ \
curl \
iputils-ping \
netcat-openbsd \
@@ -15,20 +41,12 @@ ARG TOOLS=" \
vim \
wget"
RUN set -ex \
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
set -ex \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& apt-get clean all \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.org/simple
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
COPY poetry.lock pyproject.toml ./
RUN set -ex \
&& . /opt/py3/bin/activate \
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
&& poetry install --only xpack \
&& poetry cache clear pypi --all
&& apt-get -y install --no-install-recommends ${TOOLS}
COPY --from=build-core /opt/py3 /opt/py3
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack

View File

@@ -10,29 +10,15 @@
[![][github-release-shield]][github-release-link]
[![][github-stars-shield]][github-stars-link]
**English** · [简体中文](./README.zh-CN.md)
**English** · [简体中文](./README.zh-CN.md) · [Documents][docs-link] · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]
</div>
<br/>
## What is JumpServer?
JumpServer is an open-source Privileged Access Management (PAM) tool that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.
JumpServer is an open-source Privileged Access Management (PAM) tool that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and Remote App endpoints through a web browser.
![JumpServer Overview](https://github.com/jumpserver/jumpserver/assets/32935519/35a371cb-8590-40ed-88ec-f351f8cf9045)
## Quickstart
Prepare a clean Linux Server ( 64 bit, >= 4c8g )
```sh
curl -sSL https://github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
```
Access JumpServer in your browser at `http://your-jumpserver-ip/`
- Username: `admin`
- Password: `ChangeMe`
[![JumpServer Quickstart](https://github.com/user-attachments/assets/0f32f52b-9935-485e-8534-336c63389612)](https://www.youtube.com/watch?v=UlGYRbKrpgY "JumpServer Quickstart")
![JumpServer Overview](https://github.com/jumpserver/jumpserver/assets/41712985/eb9e6f39-3911-4d4a-bca9-aa50cc3b761d)
## Screenshots
@@ -57,6 +43,15 @@ Access JumpServer in your browser at `http://your-jumpserver-ip/`
</tr>
</table>
## Quick Start
Prepare a clean Linux Server ( 64bit, >= 4c8g )
```
curl -sSL https://github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
```
Access JumpServer in your browser at `http://your-ip/`, `admin`/`admin`
## Components
JumpServer consists of multiple key components, which collectively form the functional framework of JumpServer, providing users with comprehensive capabilities for operations management and security control.
@@ -68,10 +63,12 @@ JumpServer consists of multiple key components, which collectively form the func
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer Character Protocol Connector |
| [Lion](https://github.com/jumpserver/lion) | <a href="https://github.com/jumpserver/lion/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion.svg" /></a> | JumpServer Graphical Protocol Connector |
| [Chen](https://github.com/jumpserver/chen) | <a href="https://github.com/jumpserver/chen/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen.svg" /> | JumpServer Web DB |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE RDP Proxy Connector |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Linux) |
| [Magnus](https://github.com/jumpserver/magnus) | <img alt="Magnus" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Database Proxy Connector |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer RDP Proxy Connector |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Remote Application Connector (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Remote Application Connector (Linux) |
| [Magnus](https://github.com/jumpserver/magnus) | <img alt="Magnus" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Database Proxy Connector |
## Contributing
@@ -94,8 +91,8 @@ https://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an " AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
<!-- JumpServer official link -->
[docs-link]: https://jumpserver.com/docs
[discord-link]: https://discord.com/invite/W6vYXmAQG2
[docs-link]: https://en-docs.jumpserver.org/
[discord-link]: https://discord.com/invite/jcM5tKWJ
[contributing-link]: https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md
<!-- JumpServer Other link-->

View File

@@ -12,13 +12,13 @@
<p align="center">
10 年时间,倾情投入,用心做好一款开源堡垒机。
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
------------------------------
## JumpServer 是什么?
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
- **SSH**: Linux / Unix / 网络设备 等;
- **Windows**: Web 方式连接 / 原生 RDP 连接;
@@ -38,13 +38,6 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
- **多租户**: 一套系统,多个子公司或部门同时使用;
- **云端存储**: 审计录像云端存储,永不丢失;
## 快速开始
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [产品文档](https://docs.jumpserver.org)
- [在线学习](https://edu.fit2cloud.com/page/2635362)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
## UI 展示
![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png)
@@ -59,6 +52,13 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
| 请勿修改体验环境用户的密码! |
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
## 快速开始
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [产品文档](https://docs.jumpserver.org)
- [在线学习](https://edu.fit2cloud.com/page/2635362)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
## 案例研究
- [腾讯音乐娱乐集团基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
@@ -81,24 +81,28 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
## 参与贡献
### 参与贡献
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
## 组件项目
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core API 通信的组件项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
| Project | Status | Description |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer Character Protocol Connector |
| [Lion](https://github.com/jumpserver/lion) | <a href="https://github.com/jumpserver/lion/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion.svg" /></a> | JumpServer Graphical Protocol Connector |
| [Chen](https://github.com/jumpserver/chen) | <a href="https://github.com/jumpserver/chen/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen.svg" /> | JumpServer Web DB |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE RDP Proxy Connector |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Linux) |
| [Magnus](https://github.com/jumpserver/magnus) | <img alt="Magnus" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Database Proxy Connector |
## 安全说明
JumpServer是一款安全产品请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/)

View File

@@ -4,9 +4,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test MySQL connection
@@ -16,9 +13,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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
@@ -33,9 +30,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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: "%"
@@ -50,7 +47,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test PostgreSQL connection
@@ -15,10 +11,6 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available
@@ -36,10 +28,6 @@
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
@@ -51,7 +39,3 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"

View File

@@ -14,25 +14,26 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
append: yes
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed or params.modify_sudo
- params.sudo
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user:
@@ -42,40 +43,14 @@
ignore_errors: true
when: account.secret_type == "password"
- name: "Get home directory for {{ account.username }}"
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
register: home_dir
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Check if home directory exists for {{ account.username }}"
ansible.builtin.stat:
path: "{{ home_dir.stdout.strip() }}"
register: home_dir_stat
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Ensure {{ account.username }} home directory exists"
ansible.builtin.file:
path: "{{ home_dir.stdout.strip() }}"
state: directory
owner: "{{ account.username }}"
group: "{{ account.username }}"
mode: '0750'
when:
- account.secret_type == "ssh_key"
- home_dir_stat.stat.exists == false
ignore_errors: yes
- name: Remove jumpserver ssh key
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -84,6 +59,17 @@
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
@@ -93,7 +79,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
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('') }}"
@@ -109,7 +95,7 @@
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(None) }}"
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

@@ -5,12 +5,6 @@ type:
- AIX
method: change_secret
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -34,23 +28,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
- name: uid
type: str
label: "{{ 'Params uid label' | trans }}"
default: ''
help_text: "{{ 'Params uid 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)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -66,16 +49,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params uid help text:
zh: '请输入用户ID'
ja: 'ユーザーIDを入力してください'
en: 'Please enter the user ID'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
@@ -86,7 +59,3 @@ i18n:
ja: 'グループ'
en: 'Groups'
Params uid label:
zh: '用户ID'
ja: 'ユーザーID'
en: 'User ID'

View File

@@ -14,25 +14,26 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
append: yes
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed or params.modify_sudo
- params.sudo
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user:
@@ -42,40 +43,14 @@
ignore_errors: true
when: account.secret_type == "password"
- name: "Get home directory for {{ account.username }}"
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
register: home_dir
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Check if home directory exists for {{ account.username }}"
ansible.builtin.stat:
path: "{{ home_dir.stdout.strip() }}"
register: home_dir_stat
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Ensure {{ account.username }} home directory exists"
ansible.builtin.file:
path: "{{ home_dir.stdout.strip() }}"
state: directory
owner: "{{ account.username }}"
group: "{{ account.username }}"
mode: '0750'
when:
- account.secret_type == "ssh_key"
- home_dir_stat.stat.exists == false
ignore_errors: yes
- name: Remove jumpserver ssh key
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -84,6 +59,17 @@
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
@@ -93,7 +79,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
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('') }}"
@@ -109,7 +95,7 @@
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(None) }}"
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

@@ -6,12 +6,6 @@ type:
- linux
method: change_secret
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -36,23 +30,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
- name: uid
type: str
label: "{{ 'Params uid label' | trans }}"
default: ''
help_text: "{{ 'Params uid 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)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -68,16 +51,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params uid help text:
zh: '请输入用户ID'
ja: 'ユーザーIDを入力してください'
en: 'Please enter the user ID'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
@@ -88,7 +61,3 @@ i18n:
ja: 'グループ'
en: 'Groups'
Params uid label:
zh: '用户ID'
ja: 'ユーザーID'
en: 'User ID'

View File

@@ -25,11 +25,11 @@
- name: Verify password (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.origin_address }}"
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 }}"
gateway_args: "{{ jms_gateway | default({}) }}"
login_private_key_path: "{{ account.private_key_path }}"
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -50,6 +50,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
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['regexp'] = '.*{}$'.format(secret.split()[2].strip())
return kwargs
@@ -127,7 +130,6 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
comment=f'{account.username}@{asset.address}'
)
records.append(recorder)
else:

View File

@@ -3,9 +3,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -15,9 +12,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -15,10 +11,6 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
filter: "roles"
register: db_info

View File

@@ -31,7 +31,7 @@ class GatherAccountsFilter:
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} \w{3}\s+\d{1,2} \d{2}:\d{2}:\d{2} \d{4}')
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)
@@ -46,8 +46,7 @@ class GatherAccountsFilter:
result[username].update({'address': ip_addr})
login_times = login_time_pattern.findall(line)
if login_times:
datetime_str = login_times[0].split(' ', 1)[1] + " +0800"
date = timezone.datetime.strptime(datetime_str, '%b %d %H:%M:%S %Y %z')
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
result[username].update({'date': date})
return result

View File

@@ -95,14 +95,12 @@ class GatherAccountsManager(AccountBasePlaybookManager):
return None, None
users = User.objects.filter(id__in=recipients)
if not users.exists():
if not users:
return users, None
asset_ids = self.asset_username_mapper.keys()
assets = Asset.objects.filter(id__in=asset_ids).prefetch_related('accounts')
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')))
@@ -111,24 +109,26 @@ class GatherAccountsManager(AccountBasePlaybookManager):
for asset_id, username in asset_id_username:
system_asset_username_mapper[str(asset_id)].add(username)
change_info = defaultdict(dict)
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[str(asset_id_map[asset_id])] = {
'add_usernames': add_usernames,
'remove_usernames': remove_usernames
change_info[k] = {
'add_usernames': ', '.join(add_usernames),
'remove_usernames': ', '.join(remove_usernames),
}
return users, dict(change_info)
return users, change_info
@staticmethod
def send_email_if_need(users, change_info):

View File

@@ -4,9 +4,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test MySQL connection
@@ -16,9 +13,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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
@@ -33,9 +30,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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: "%"
@@ -50,7 +47,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test PostgreSQL connection
@@ -15,10 +11,6 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available
@@ -36,10 +28,6 @@
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
@@ -52,10 +40,6 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
when:
- result is succeeded
- change_info is succeeded

View File

@@ -14,25 +14,26 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
append: yes
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed or params.modify_sudo
- params.sudo
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user:
@@ -42,40 +43,14 @@
ignore_errors: true
when: account.secret_type == "password"
- name: "Get home directory for {{ account.username }}"
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
register: home_dir
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Check if home directory exists for {{ account.username }}"
ansible.builtin.stat:
path: "{{ home_dir.stdout.strip() }}"
register: home_dir_stat
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Ensure {{ account.username }} home directory exists"
ansible.builtin.file:
path: "{{ home_dir.stdout.strip() }}"
state: directory
owner: "{{ account.username }}"
group: "{{ account.username }}"
mode: '0750'
when:
- account.secret_type == "ssh_key"
- home_dir_stat.stat.exists == false
ignore_errors: yes
- name: Remove jumpserver ssh key
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -84,6 +59,17 @@
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
@@ -93,7 +79,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
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('') }}"
@@ -109,7 +95,7 @@
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(None) }}"
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

@@ -5,12 +5,6 @@ type:
- AIX
method: push_account
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -34,23 +28,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
- name: uid
type: str
label: "{{ 'Params uid label' | trans }}"
default: ''
help_text: "{{ 'Params uid 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)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -66,16 +49,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params uid help text:
zh: '请输入用户ID'
ja: 'ユーザーIDを入力してください'
en: 'Please enter the user ID'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
@@ -86,7 +59,3 @@ i18n:
ja: 'グループ'
en: 'Groups'
Params uid label:
zh: '用户ID'
ja: 'ユーザーID'
en: 'User ID'

View File

@@ -14,25 +14,26 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
append: yes
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed or params.modify_sudo
- params.sudo
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user:
@@ -42,40 +43,14 @@
ignore_errors: true
when: account.secret_type == "password"
- name: "Get home directory for {{ account.username }}"
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
register: home_dir
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Check if home directory exists for {{ account.username }}"
ansible.builtin.stat:
path: "{{ home_dir.stdout.strip() }}"
register: home_dir_stat
when: account.secret_type == "ssh_key"
ignore_errors: yes
- name: "Ensure {{ account.username }} home directory exists"
ansible.builtin.file:
path: "{{ home_dir.stdout.strip() }}"
state: directory
owner: "{{ account.username }}"
group: "{{ account.username }}"
mode: '0750'
when:
- account.secret_type == "ssh_key"
- home_dir_stat.stat.exists == false
ignore_errors: yes
- name: Remove jumpserver ssh key
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -84,6 +59,17 @@
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
@@ -93,7 +79,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
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('') }}"
@@ -109,7 +95,7 @@
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(None) }}"
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

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

View File

@@ -25,11 +25,11 @@
- name: Verify password (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.origin_address }}"
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 }}"
gateway_args: "{{ jms_gateway | default({}) }}"
login_private_key_path: "{{ account.private_key_path }}"
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -3,9 +3,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: "Remove account"
@@ -15,8 +12,8 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: "Remove account"
@@ -16,8 +12,4 @@
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
state: absent

View File

@@ -13,3 +13,4 @@
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"

View File

@@ -3,9 +3,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Verify account
@@ -15,7 +12,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Verify account
@@ -15,9 +11,5 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available

View File

@@ -1,5 +1,3 @@
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from assets.automations.ping_gateway.manager import PingGatewayManager
from common.utils import get_logger
@@ -15,7 +13,7 @@ class VerifyGatewayAccountManager(PingGatewayManager):
@staticmethod
def before_runner_start():
logger.info(_(">>> Start executing the task to test gateway account connectivity"))
logger.info(">>> 开始执行测试网关账号可连接性任务")
def get_accounts(self, gateway):
account_ids = self.execution.snapshot['accounts']

View File

@@ -1,21 +1,19 @@
from importlib import import_module
from django.utils.functional import LazyObject, empty
from django.utils.functional import LazyObject
from common.utils import get_logger
from ..const import VaultTypeChoices
__all__ = ['vault_client', 'get_vault_client', 'refresh_vault_client']
__all__ = ['vault_client', 'get_vault_client']
logger = get_logger(__file__)
def get_vault_client(raise_exception=False, **kwargs):
tp = kwargs.get('VAULT_BACKEND') if kwargs.get('VAULT_ENABLED') else VaultTypeChoices.local
# TODO: Temporary processing, subsequent deletion
tp = VaultTypeChoices.local if tp == VaultTypeChoices.azure else tp
enabled = kwargs.get('VAULT_ENABLED')
tp = 'hcp' if enabled else 'local'
try:
module_path = f'apps.accounts.backends.{tp}.main'
client = import_module(module_path).Vault(**kwargs)
@@ -41,7 +39,3 @@ class VaultClient(LazyObject):
""" 为了安全, 页面修改配置, 重启服务后才会重新初始化 vault_client """
vault_client = VaultClient()
def refresh_vault_client():
vault_client._wrapped = empty

View File

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

View File

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

View File

@@ -1,57 +0,0 @@
from common.db.utils import get_logger
from .entries import build_entry
from .service import AZUREVaultClient
from ..base import BaseVault
from ...const import VaultTypeChoices
logger = get_logger(__name__)
__all__ = ['Vault']
class Vault(BaseVault):
type = VaultTypeChoices.azure
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = AZUREVaultClient(
vault_url=kwargs.get('VAULT_AZURE_HOST'),
tenant_id=kwargs.get('VAULT_AZURE_TENANT_ID'),
client_id=kwargs.get('VAULT_AZURE_CLIENT_ID'),
client_secret=kwargs.get('VAULT_AZURE_CLIENT_SECRET')
)
def is_active(self):
return self.client.is_active()
def _get(self, instance):
entry = build_entry(instance)
secret = self.client.get(name=entry.full_path)
secret = entry.to_external_data(secret)
return secret
def _create(self, instance):
entry = build_entry(instance)
secret = entry.to_internal_data()
self.client.create(name=entry.full_path, secret=secret)
def _update(self, instance):
entry = build_entry(instance)
secret = entry.to_internal_data()
self.client.update(name=entry.full_path, secret=secret)
def _delete(self, instance):
entry = build_entry(instance)
self.client.delete(name=entry.full_path)
def _clean_db_secret(self, instance):
instance.is_sync_metadata = False
instance.mark_secret_save_to_vault()
def _save_metadata(self, instance, metadata):
try:
entry = build_entry(instance)
self.client.update_metadata(name=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
#
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
from azure.identity import ClientSecretCredential
from azure.keyvault.secrets import SecretClient
from common.utils import get_logger
logger = get_logger(__name__)
__all__ = ['AZUREVaultClient']
class AZUREVaultClient(object):
def __init__(self, vault_url, tenant_id, client_id, client_secret):
authentication_endpoint = 'https://login.microsoftonline.com/' \
if ('azure.net' in vault_url) else 'https://login.chinacloudapi.cn/'
credentials = ClientSecretCredential(
client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, authority=authentication_endpoint
)
self.client = SecretClient(vault_url=vault_url, credential=credentials)
def is_active(self):
try:
self.client.set_secret('jumpserver', '666')
except (ResourceNotFoundError, ClientAuthenticationError) as e:
logger.error(str(e))
return False, f'Vault is not reachable: {e}'
else:
return True, ''
def get(self, name, version=None):
try:
secret = self.client.get_secret(name, version)
return secret.value
except (ResourceNotFoundError, ClientAuthenticationError) as e:
logger.error(f'get: {name} {str(e)}')
return ''
def create(self, name, secret):
if not secret:
secret = ''
self.client.set_secret(name, secret)
def update(self, name, secret):
if not secret:
secret = ''
self.client.set_secret(name, secret)
def delete(self, name):
self.client.begin_delete_secret(name)
def update_metadata(self, name, metadata: dict):
try:
self.client.update_secret_properties(name, tags=metadata)
except (ResourceNotFoundError, ClientAuthenticationError) as e:
logger.error(f'update_metadata: {name} {str(e)}')

View File

@@ -10,11 +10,6 @@ class BaseVault(ABC):
def __init__(self, *args, **kwargs):
self.enabled = kwargs.get('VAULT_ENABLED')
@property
@abstractmethod
def type(self):
raise NotImplementedError
def get(self, instance):
""" 返回 secret 值 """
return self._get(instance)
@@ -25,6 +20,9 @@ class BaseVault(ABC):
self._clean_db_secret(instance)
self.save_metadata(instance)
if instance.is_sync_metadata:
self.save_metadata(instance)
def update(self, instance):
if not instance.secret_has_save_to_vault:
self._update(instance)

View File

@@ -3,16 +3,12 @@ from .entries import build_entry
from .service import VaultKVClient
from ..base import BaseVault
from ...const import VaultTypeChoices
__all__ = ['Vault']
logger = get_logger(__name__)
__all__ = ['Vault']
class Vault(BaseVault):
type = VaultTypeChoices.hcp
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = VaultKVClient(

View File

@@ -1,6 +1,5 @@
from common.utils import get_logger
from ..base import BaseVault
from ...const import VaultTypeChoices
logger = get_logger(__name__)
@@ -8,7 +7,6 @@ __all__ = ['Vault']
class Vault(BaseVault):
type = VaultTypeChoices.local
def is_active(self):
return True, ''

View File

@@ -49,9 +49,9 @@ class SecretStrategy(models.TextChoices):
class SSHKeyStrategy(models.TextChoices):
set_jms = 'set_jms', _('Replace (Replace only keys pushed by JumpServer) ')
set = 'set', _('Empty and append SSH KEY')
add = 'add', _('Append SSH KEY')
set = 'set', _('Empty and append SSH KEY')
set_jms = 'set_jms', _('Replace (Replace only keys pushed by JumpServer) ')
class TriggerChoice(models.TextChoices, TreeChoices):

View File

@@ -7,4 +7,3 @@ __all__ = ['VaultTypeChoices']
class VaultTypeChoices(models.TextChoices):
local = 'local', _('Database')
hcp = 'hcp', _('HCP Vault')
azure = 'azure', _('Azure Key Vault')

View File

@@ -10,6 +10,7 @@ import common.db.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
@@ -25,19 +26,13 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity',
models.CharField(choices=[('-', 'Unknown'), ('ok', 'OK'), ('err', 'Error')], default='-',
max_length=16, verbose_name='Connectivity')),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', models.CharField(choices=[('-', 'Unknown'), ('ok', 'OK'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, verbose_name='Secret type')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('version', models.IntegerField(default=0, verbose_name='Version')),
@@ -46,11 +41,7 @@ class Migration(migrations.Migration):
],
options={
'verbose_name': 'Account',
'permissions': [('view_accountsecret', 'Can view asset account secret'),
('view_historyaccount', 'Can view asset history account'),
('view_historyaccountsecret', 'Can view asset history account secret'),
('verify_account', 'Can verify account'), ('push_account', 'Can push account'),
('remove_account', 'Can remove account')],
'permissions': [('view_accountsecret', 'Can view asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret'), ('verify_account', 'Can verify account'), ('push_account', 'Can push account'), ('remove_account', 'Can remove account')],
},
),
migrations.CreateModel(
@@ -62,21 +53,16 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab')),
('types', models.JSONField(default=list)),
('backup_type',
models.CharField(choices=[('email', 'Email'), ('object_storage', 'SFTP')], default='email',
max_length=128, verbose_name='Backup type')),
('backup_type', models.CharField(choices=[('email', 'Email'), ('object_storage', 'SFTP')], default='email', max_length=128, verbose_name='Backup type')),
('is_password_divided_by_email', models.BooleanField(default=True, verbose_name='Password divided')),
('is_password_divided_by_obj_storage',
models.BooleanField(default=True, verbose_name='Password divided')),
('zip_encrypt_password', common.db.fields.EncryptCharField(blank=True, max_length=4096, null=True,
verbose_name='Zip encrypt password')),
('is_password_divided_by_obj_storage', models.BooleanField(default=True, verbose_name='Password divided')),
('zip_encrypt_password', common.db.fields.EncryptCharField(blank=True, max_length=4096, null=True, verbose_name='Zip encrypt password')),
],
options={
'verbose_name': 'Account backup plan',
@@ -86,16 +72,12 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AccountBackupExecution',
fields=[
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')),
('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')),
('snapshot',
models.JSONField(blank=True, default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True,
verbose_name='Account backup snapshot')),
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')],
default='manual', max_length=128, verbose_name='Trigger mode')),
('snapshot', models.JSONField(blank=True, default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True, verbose_name='Account backup snapshot')),
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')),
('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')),
('is_success', models.BooleanField(default=False, verbose_name='Is success')),
],
@@ -113,19 +95,13 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('secret_strategy',
models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')],
default='specific', max_length=16, verbose_name='Secret strategy')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, verbose_name='Secret type')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('auto_push', models.BooleanField(default=False, verbose_name='Auto push')),
@@ -166,8 +142,7 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('present', models.BooleanField(default=True, verbose_name='Present')),
('date_last_login', models.DateTimeField(null=True, verbose_name='Date login')),
('username', models.CharField(blank=True, db_index=True, max_length=32, verbose_name='Username')),
@@ -183,16 +158,12 @@ class Migration(migrations.Migration):
fields=[
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16, verbose_name='Secret type')),
('version', models.IntegerField(default=0, verbose_name='Version')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type',
models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
],
options={
'verbose_name': 'historical Account',
@@ -210,11 +181,8 @@ class Migration(migrations.Migration):
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('alias', models.CharField(
choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account'),
('@SPEC', 'Specified account')], max_length=128, verbose_name='Alias')),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account'), ('@SPEC', 'Specified account')], max_length=128, verbose_name='Alias')),
('secret_from_login', models.BooleanField(default=None, null=True, verbose_name='Secret from login')),
],
options={'verbose_name': 'Virtual account'},

View File

@@ -50,7 +50,7 @@ class Migration(migrations.Migration):
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('ssh_key_change_strategy', models.CharField(choices=[('set_jms', 'Replace (Replace only keys pushed by JumpServer) '), ('set', 'Empty and append SSH KEY'), ('add', 'Append SSH KEY')], default='set_jms', max_length=16, verbose_name='SSH key change strategy')),
('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy')),
],
options={
'verbose_name': 'Change secret automation',
@@ -76,7 +76,7 @@ class Migration(migrations.Migration):
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('ssh_key_change_strategy', models.CharField(choices=[('set_jms', 'Replace (Replace only keys pushed by JumpServer) '), ('set', 'Empty and append SSH KEY'), ('add', 'Append SSH KEY')], default='set_jms', max_length=16, verbose_name='SSH key change strategy')),
('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy')),
('triggers', models.JSONField(default=list, max_length=16, verbose_name='Triggers')),
('username', models.CharField(max_length=128, verbose_name='Username')),
('action', models.CharField(max_length=16, verbose_name='Action')),

View File

@@ -1,30 +0,0 @@
# Generated by Django 4.1.13 on 2024-08-26 09:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0005_myasset'),
('accounts', '0003_automation'),
]
operations = [
migrations.AlterField(
model_name='changesecretrecord',
name='account',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.account'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='asset',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.asset'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='execution',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.automationexecution'),
),
]

View File

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

View File

@@ -51,7 +51,7 @@ class AutomationExecution(AssetAutomationExecution):
class ChangeSecretMixin(SecretWithRandomMixin):
ssh_key_change_strategy = models.CharField(
choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.set_jms, verbose_name=_('SSH key change strategy')
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
)
get_all_assets: callable # get all assets

View File

@@ -33,15 +33,16 @@ class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
class ChangeSecretRecord(JMSBaseModel):
execution = models.ForeignKey('accounts.AutomationExecution', on_delete=models.SET_NULL, null=True)
asset = models.ForeignKey('assets.Asset', on_delete=models.SET_NULL, null=True)
account = models.ForeignKey('accounts.Account', on_delete=models.SET_NULL, null=True)
execution = models.ForeignKey('accounts.AutomationExecution', on_delete=models.CASCADE)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
status = models.CharField(
max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value
max_length=16, verbose_name=_('Status'),
default=ChangeSecretRecordStatusChoice.pending.value
)
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
@@ -50,4 +51,4 @@ class ChangeSecretRecord(JMSBaseModel):
verbose_name = _("Change secret record")
def __str__(self):
return f'{self.account.username}@{self.asset}'
return self.account.__str__()

View File

@@ -2,7 +2,7 @@ from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes, SecretType
from accounts.const import AutomationTypes
from accounts.models import Account
from .base import AccountBaseAutomation
from .change_secret import ChangeSecretMixin
@@ -23,8 +23,7 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
create_usernames = set(usernames) - set(account_usernames)
create_account_objs = [
Account(
name=f"{username}-{secret_type}" if secret_type != SecretType.PASSWORD else username,
username=username,
name=f'{username}-{secret_type}', username=username,
secret_type=secret_type, asset=asset,
)
for username in create_usernames

View File

@@ -81,28 +81,21 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
@staticmethod
def get_template_attr_for_account(template):
# Set initial data from template
field_names = [
'name', 'username',
'secret_type', 'secret',
'privileged', 'is_active'
'name', 'username', 'secret', 'push_params',
'secret_type', 'privileged', 'is_active'
]
field_map = {
'push_params': 'params',
'auto_push': 'push_now'
}
field_names.extend(field_map.keys())
attrs = {}
for name in field_names:
value = getattr(template, name, None)
if value is None:
continue
attr_name = field_map.get(name, name)
attrs[attr_name] = value
if name == 'push_params':
attrs['params'] = value
else:
attrs[name] = value
attrs['secret'] = template.get_secret()
return attrs
@@ -178,15 +171,14 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
instance.save()
return instance, 'updated'
else:
raise serializers.ValidationError(_('Account already exists'))
raise serializers.ValidationError('Account already exists')
def create(self, validated_data):
push_now = validated_data.pop('push_now', None)
params = validated_data.pop('params', None)
self.clean_auth_fields(validated_data)
instance, stat = self.do_create(validated_data)
if instance.source == Source.LOCAL:
self.push_account_if_need(instance, push_now, params, stat)
self.push_account_if_need(instance, push_now, params, stat)
return instance
def update(self, instance, validated_data):
@@ -247,7 +239,6 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
'name': {'required': False},
'source_id': {'required': False, 'allow_null': True},
}
fields_unimport_template = ['params']
@classmethod
def setup_eager_loading(cls, queryset):
@@ -289,8 +280,8 @@ class AssetAccountBulkSerializer(
fields = [
'name', 'username', 'secret', 'secret_type', 'passphrase',
'privileged', 'is_active', 'comment', 'template',
'on_invalid', 'push_now', 'params', 'assets',
'su_from_username', 'source', 'source_id',
'on_invalid', 'push_now', 'assets', 'su_from_username',
'source', 'source_id',
]
extra_kwargs = {
'name': {'required': False},
@@ -428,23 +419,16 @@ class AssetAccountBulkSerializer(
return results
@staticmethod
def push_accounts_if_need(results, push_now, params):
def push_accounts_if_need(results, push_now):
if not push_now:
return
account_ids = [v['instance'] for v in results if v.get('instance')]
accounts = Account.objects.filter(id__in=account_ids, source=Source.LOCAL)
if not accounts.exists():
return
account_ids = [str(_id) for _id in accounts.values_list('id', flat=True)]
push_accounts_to_assets_task.delay(account_ids, params)
accounts = [str(v['instance']) for v in results if v.get('instance')]
push_accounts_to_assets_task.delay(accounts)
def create(self, validated_data):
params = validated_data.pop('params', None)
push_now = validated_data.pop('push_now', False)
results = self.perform_bulk_create(validated_data)
self.push_accounts_if_need(results, push_now, params)
self.push_accounts_if_need(results, push_now)
for res in results:
res['asset'] = str(res['asset'])
return results

View File

@@ -10,7 +10,7 @@ from .base import BaseAccountSerializer
class PasswordRulesSerializer(serializers.Serializer):
length = serializers.IntegerField(min_value=8, max_value=36, default=16, label=_('Password length'))
length = serializers.IntegerField(min_value=8, max_value=30, default=16, label=_('Password length'))
lowercase = serializers.BooleanField(default=True, label=_('Lowercase'))
uppercase = serializers.BooleanField(default=True, label=_('Uppercase'))
digit = serializers.BooleanField(default=True, label=_('Digit'))
@@ -19,16 +19,6 @@ class PasswordRulesSerializer(serializers.Serializer):
default='', allow_blank=True, max_length=16, label=_('Exclude symbol')
)
@staticmethod
def get_render_help_text():
return _("""length is the length of the password, and the range is 8 to 30.
lowercase indicates whether the password contains lowercase letters,
uppercase indicates whether it contains uppercase letters,
digit indicates whether it contains numbers, and symbol indicates whether it contains special symbols.
exclude_symbols is used to exclude specific symbols. You can fill in the symbol characters to be excluded (up to 16).
If you do not need to exclude symbols, you can leave it blank.
default: {"length": 16, "lowercase": true, "uppercase": true, "digit": true, "symbol": true, "exclude_symbols": ""}""")
class AccountTemplateSerializer(BaseAccountSerializer):
password_rules = PasswordRulesSerializer(required=False, label=_('Password rules'))
@@ -56,7 +46,6 @@ class AccountTemplateSerializer(BaseAccountSerializer):
'required': False
},
}
fields_unimport_template = ['push_params']
@staticmethod
def generate_secret(attrs):

View File

@@ -63,26 +63,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
)},
}}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_ssh_key_change_strategy_choices()
def set_ssh_key_change_strategy_choices(self):
ssh_key_change_strategy = self.fields.get("ssh_key_change_strategy")
if not ssh_key_change_strategy:
return
ssh_key_change_strategy._choices.pop(SSHKeyStrategy.add, None)
def to_representation(self, instance):
data = super().to_representation(instance)
ssh_strategy_value = data.get('ssh_key_change_strategy', {}).get('value')
if ssh_strategy_value == SSHKeyStrategy.add:
data['ssh_key_change_strategy'] = {
'label': SSHKeyStrategy.set_jms.label,
'value': SSHKeyStrategy.set_jms.value
}
return data
@property
def model_type(self):
return AutomationTypes.change_secret
@@ -95,6 +75,19 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
if self.initial_data.get('secret_strategy') == SecretStrategy.custom:
return password_rules
length = password_rules.get('length')
try:
length = int(length)
except Exception as e:
logger.error(e)
msg = _("* Please enter the correct password length")
raise serializers.ValidationError(msg)
if length < 6 or length > 30:
msg = _('* Password length range 6-30 bits')
raise serializers.ValidationError(msg)
return password_rules
def validate(self, attrs):

View File

@@ -3,17 +3,13 @@ from collections import defaultdict
from django.db.models.signals import post_delete
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils.functional import LazyObject
from django.utils.translation import gettext_noop
from accounts.backends import vault_client, refresh_vault_client
from accounts.const import Source
from accounts.backends import vault_client
from audits.const import ActivityChoices
from audits.signal_handlers import create_activities
from common.decorators import merge_delay_run
from common.signals import django_ready
from common.utils import get_logger, i18n_fmt
from common.utils.connection import RedisPubSub
from .models import Account, AccountTemplate
from .tasks.push_account import push_accounts_to_assets_task
@@ -36,7 +32,7 @@ def push_accounts_if_need(accounts=()):
template_accounts = defaultdict(list)
for ac in accounts:
# 再强调一次吧
if ac.source != Source.TEMPLATE:
if ac.source != 'template':
continue
template_accounts[ac.source_id].append(ac)
@@ -65,7 +61,7 @@ def create_accounts_activities(account, action='create'):
@receiver(post_save, sender=Account)
def on_account_create_by_template(sender, instance, created=False, **kwargs):
if not created or instance.source != Source.TEMPLATE:
if not created or instance.source != 'template':
return
push_accounts_if_need.delay(accounts=(instance,))
create_accounts_activities(instance, action='create')
@@ -94,18 +90,3 @@ class VaultSignalHandler(object):
for model in (Account, AccountTemplate, Account.history.model):
post_save.connect(VaultSignalHandler.save_to_vault, sender=model)
post_delete.connect(VaultSignalHandler.delete_to_vault, sender=model)
class VaultPubSub(LazyObject):
def _setup(self):
self._wrapped = RedisPubSub('refresh_vault')
vault_pub_sub = VaultPubSub()
@receiver(django_ready)
def subscribe_vault_change(sender, **kwargs):
logger.debug("Start subscribe vault change")
vault_pub_sub.subscribe(lambda name: refresh_vault_client())

View File

@@ -1,15 +1,9 @@
import datetime
from celery import shared_task
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _, gettext_noop
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot
from common.const.crontab import CRONTAB_AT_AM_THREE
from common.utils import get_logger, get_object_or_none, get_log_keep_day
from ops.celery.decorator import register_as_period_task
from common.utils import get_logger, get_object_or_none
from orgs.utils import tmp_to_org, tmp_to_root_org
logger = get_logger(__file__)
@@ -28,14 +22,8 @@ def task_activity_callback(self, pid, trigger, tp, *args, **kwargs):
@shared_task(
queue='ansible',
verbose_name=_('Account execute automation'),
activity_callback=task_activity_callback,
description=_(
"""Unified execution entry for account automation tasks: when the system performs tasks
such as account push, password change, account verification, account collection,
and gateway account verification, all tasks are executed through this unified entry"""
)
queue='ansible', verbose_name=_('Account execute automation'),
activity_callback=task_activity_callback
)
def execute_account_automation_task(pid, trigger, tp):
model = AutomationTypes.get_type_model(tp)
@@ -60,12 +48,8 @@ def record_task_activity_callback(self, record_ids, *args, **kwargs):
@shared_task(
queue='ansible',
verbose_name=_('Execute automation record'),
activity_callback=record_task_activity_callback,
description=_(
"""When manually executing password change records, this task is used"""
)
queue='ansible', verbose_name=_('Execute automation record'),
activity_callback=record_task_activity_callback
)
def execute_automation_record_task(record_ids, tp):
from accounts.models import ChangeSecretRecord
@@ -90,33 +74,3 @@ def execute_automation_record_task(record_ids, tp):
}
with tmp_to_org(record.execution.org_id):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
@shared_task(
verbose_name=_('Clean change secret and push record period'),
description=_(
"""The system will periodically clean up unnecessary password change and push records,
including their associated change tasks, execution logs, assets, and accounts. When any
of these associated items are deleted, the corresponding password change and push records
become invalid. Therefore, to maintain a clean and efficient database, the system will
clean up expired records at 2 a.m daily, based on the interval specified by
PERM_EXPIRED_CHECK_PERIODIC in the config.txt configuration file. This periodic cleanup
mechanism helps free up storage space and enhances the security and overall performance
of data management"""
)
)
@register_as_period_task(crontab=CRONTAB_AT_AM_THREE)
def clean_change_secret_and_push_record_period():
from accounts.models import ChangeSecretRecord
print('Start clean change secret and push record period')
with tmp_to_root_org():
now = timezone.now()
days = get_log_keep_day('ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS')
expired_day = now - datetime.timedelta(days=days)
records = ChangeSecretRecord.objects.filter(
date_updated__lt=expired_day
).filter(
Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True)
)
records.delete()

View File

@@ -22,13 +22,7 @@ def task_activity_callback(self, pid, trigger, *args, **kwargs):
return resource_ids, org_id
@shared_task(
verbose_name=_('Execute account backup plan'),
activity_callback=task_activity_callback,
description=_(
"When performing scheduled or manual account backups, this task is used"
)
)
@shared_task(verbose_name=_('Execute account backup plan'), activity_callback=task_activity_callback)
def execute_account_backup_task(pid, trigger, **kwargs):
from accounts.models import AccountBackupAutomation
with tmp_to_root_org():

View File

@@ -26,10 +26,8 @@ def gather_asset_accounts_util(nodes, task_name):
@shared_task(
queue="ansible",
verbose_name=_('Gather asset accounts'),
activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None),
description=_("Unused")
queue="ansible", verbose_name=_('Gather asset accounts'),
activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None)
)
def gather_asset_accounts_task(node_ids, task_name=None):
if task_name is None:

View File

@@ -12,12 +12,8 @@ __all__ = [
@shared_task(
queue="ansible",
verbose_name=_('Push accounts to assets'),
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None),
description=_(
"When creating or modifying an account requires account push, this task is executed"
)
queue="ansible", verbose_name=_('Push accounts to assets'),
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
)
def push_accounts_to_assets_task(account_ids, params=None):
from accounts.models import PushAccountAutomation

View File

@@ -21,13 +21,8 @@ __all__ = ['remove_accounts_task']
@shared_task(
queue="ansible",
verbose_name=_('Remove account'),
activity_callback=lambda self, gather_account_ids, *args, **kwargs: (gather_account_ids, None),
description=_(
"""When clicking "Sync deletion" in 'Console - Gather Account - Gathered accounts' this
task will be executed"""
)
queue="ansible", verbose_name=_('Remove account'),
activity_callback=lambda self, gather_account_ids, *args, **kwargs: (gather_account_ids, None)
)
def remove_accounts_task(gather_account_ids):
from accounts.models import GatheredAccount
@@ -46,15 +41,7 @@ def remove_accounts_task(gather_account_ids):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
@shared_task(
verbose_name=_('Clean historical accounts'),
description=_(
"""Each time an asset account is updated, a historical account is generated, so it is
necessary to clean up the asset account history. The system will clean up excess account
records at 2 a.m. daily based on the configuration in the "System settings - Features -
Account storage - Record limit"""
)
)
@shared_task(verbose_name=_('Clean historical accounts'))
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
@tmp_to_root_org()
def clean_historical_accounts():

View File

@@ -9,11 +9,7 @@ from orgs.utils import tmp_to_root_org, tmp_to_org
@shared_task(
verbose_name=_('Template sync info to related accounts'),
activity_callback=lambda self, template_id, *args, **kwargs: (template_id, None),
description=_(
"""When clicking 'Sync new secret to accounts' in 'Console - Account - Templates -
Accounts' this task will be executed"""
)
activity_callback=lambda self, template_id, *args, **kwargs: (template_id, None)
)
def template_sync_related_accounts(template_id, user_id=None):
from accounts.models import Account, AccountTemplate

View File

@@ -5,7 +5,6 @@ from celery import shared_task
from django.utils.translation import gettext_lazy as _
from accounts.backends import vault_client
from accounts.const import VaultTypeChoices
from accounts.models import Account, AccountTemplate
from common.utils import get_logger
from orgs.utils import tmp_to_root_org
@@ -29,20 +28,12 @@ def sync_instance(instance):
return "succeeded", msg
@shared_task(
verbose_name=_('Sync secret to vault'),
description=_(
"When clicking 'Sync' in 'System Settings - Features - Account Storage' this task will be executed"
)
)
@shared_task(verbose_name=_('Sync secret to vault'))
def sync_secret_to_vault():
if not vault_client.enabled:
# 这里不能判断 settings.VAULT_ENABLED, 必须判断当前 vault_client 的类型
print('\033[35m>>> 当前 Vault 功能未开启, 不需要同步')
return
if VaultTypeChoices.local == vault_client.type:
print('\033[31m>>> 当前第三方 Vault 客户端初始化失败,数据存储在本地数据库')
return
failed, skipped, succeeded = 0, 0, 0
to_sync_models = [Account, AccountTemplate, Account.history.model]

View File

@@ -4,6 +4,7 @@ from django.utils.translation import gettext_noop
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot
from assets.const import GATEWAY_NAME
from common.utils import get_logger
from orgs.utils import org_aware_func
@@ -31,13 +32,13 @@ def verify_accounts_connectivity_util(accounts, task_name):
asset_ids = [a.asset_id for a in accounts]
assets = Asset.objects.filter(id__in=asset_ids)
gateways = assets.gateways()
gateways = assets.filter(platform__name=GATEWAY_NAME)
verify_connectivity_util(
gateways, AutomationTypes.verify_gateway_account,
accounts, task_name
)
common_assets = assets.gateways(0)
common_assets = assets.exclude(platform__name=GATEWAY_NAME)
verify_connectivity_util(
common_assets, AutomationTypes.verify_account,
accounts, task_name
@@ -45,12 +46,8 @@ def verify_accounts_connectivity_util(accounts, task_name):
@shared_task(
queue="ansible",
verbose_name=_('Verify asset account availability'),
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None),
description=_(
"When clicking 'Test' in 'Console - Asset details - Accounts' this task will be executed"
)
queue="ansible", verbose_name=_('Verify asset account availability'),
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
)
def verify_accounts_connectivity_task(account_ids):
from accounts.models import Account, VerifyAccountAutomation

View File

@@ -1,29 +1,18 @@
{% load i18n %}
<h3></h3>
<table style="width: 100%; border-collapse: collapse; table-layout: fixed; text-align: left; margin-top: 20px;">
<h3>{% trans 'Gather account change information' %}</h3>
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
<caption></caption>
<tr style="background-color: #f2f2f2;">
<th style="border: 1px solid #ddd; padding: 15px; text-align: left; vertical-align: top; line-height: 1.5;">
{% trans 'Asset' %}
</th>
<th style="border: 1px solid #ddd; padding: 15px; text-align: left; vertical-align: top; line-height: 1.5;">
{% trans 'Added account' %}
</th>
<th style="border: 1px solid #ddd; padding: 15px; text-align: left; vertical-align: top; line-height: 1.5;">
{% trans 'Deleted account' %}
</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Asset' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Added account' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Deleted account' %}</th>
</tr>
{% for name, change in change_info.items %}
<tr style="{% cycle 'background-color: #ebf5ff;' 'background-color: #fff;' %}">
<td style="border: 1px solid #ddd; padding: 10px; text-align: left; vertical-align: top;">
{{ name | safe }}
</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: left; vertical-align: top;">
{{ change.add_usernames | join:" " | safe }}
</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: left; vertical-align: top;">
{{ change.remove_usernames | join:" " | safe }}
</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ name }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ change.add_usernames }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ change.remove_usernames }}</td>
</tr>
{% endfor %}
</table>

View File

@@ -8,4 +8,3 @@ class ActionChoices(models.TextChoices):
review = 'review', _('Review')
warning = 'warning', _('Warn')
notice = 'notice', _('Notify')
notify_and_warn = 'notify_and_warn', _('Notify and warn')

View File

@@ -62,7 +62,7 @@ class ActionAclSerializer(serializers.Serializer):
self.set_action_choices()
class Meta:
action_choices_exclude = [ActionChoices.warning, ActionChoices.notify_and_warn]
action_choices_exclude = [ActionChoices.warning]
def set_action_choices(self):
field_action = self.fields.get("action")

View File

@@ -7,4 +7,3 @@ from .node import *
from .platform import *
from .protocol import *
from .tree import *
from .my_asset import *

View File

@@ -2,10 +2,10 @@
#
from collections import defaultdict
import django_filters
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
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
@@ -22,7 +22,6 @@ from common.drf.filters import BaseFilterSet, AttrRulesFilterBackend
from common.utils import get_logger, is_uuid
from orgs.mixins import generics
from orgs.mixins.api import OrgBulkModelViewSet
from ...const import GATEWAY_NAME
from ...notifications import BulkUpdatePlatformSkipAssetUserMsg
logger = get_logger(__file__)
@@ -33,32 +32,31 @@ __all__ = [
class AssetFilterSet(BaseFilterSet):
platform = drf_filters.CharFilter(method='filter_platform')
is_gateway = drf_filters.BooleanFilter(method='filter_is_gateway')
exclude_platform = drf_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
domain = drf_filters.CharFilter(method='filter_domain')
type = drf_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = drf_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
protocols = drf_filters.CharFilter(method='filter_protocols')
domain_enabled = drf_filters.BooleanFilter(
platform = django_filters.CharFilter(method='filter_platform')
exclude_platform = django_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
domain = django_filters.CharFilter(method='filter_domain')
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
protocols = django_filters.CharFilter(method='filter_protocols')
domain_enabled = django_filters.BooleanFilter(
field_name="platform__domain_enabled", lookup_expr="exact"
)
ping_enabled = drf_filters.BooleanFilter(
ping_enabled = django_filters.BooleanFilter(
field_name="platform__automation__ping_enabled", lookup_expr="exact"
)
gather_facts_enabled = drf_filters.BooleanFilter(
gather_facts_enabled = django_filters.BooleanFilter(
field_name="platform__automation__gather_facts_enabled", lookup_expr="exact"
)
change_secret_enabled = drf_filters.BooleanFilter(
change_secret_enabled = django_filters.BooleanFilter(
field_name="platform__automation__change_secret_enabled", lookup_expr="exact"
)
push_account_enabled = drf_filters.BooleanFilter(
push_account_enabled = django_filters.BooleanFilter(
field_name="platform__automation__push_account_enabled", lookup_expr="exact"
)
verify_account_enabled = drf_filters.BooleanFilter(
verify_account_enabled = django_filters.BooleanFilter(
field_name="platform__automation__verify_account_enabled", lookup_expr="exact"
)
gather_accounts_enabled = drf_filters.BooleanFilter(
gather_accounts_enabled = django_filters.BooleanFilter(
field_name="platform__automation__gather_accounts_enabled", lookup_expr="exact"
)
@@ -73,16 +71,9 @@ class AssetFilterSet(BaseFilterSet):
def filter_platform(queryset, name, value):
if value.isdigit():
return queryset.filter(platform_id=value)
elif value == GATEWAY_NAME:
return queryset.filter(platform__name__istartswith=GATEWAY_NAME)
else:
return queryset.filter(platform__name=value)
@staticmethod
def filter_is_gateway(queryset, name, value):
queryset = queryset.gateways(value)
return queryset
@staticmethod
def filter_domain(queryset, name, value):
if is_uuid(value):
@@ -123,10 +114,6 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
NodeFilterBackend, AttrRulesFilterBackend
]
def perform_destroy(self, instance):
instance.accounts.update(su_from_id=None)
instance.delete()
def get_queryset(self):
queryset = super().get_queryset()
if queryset.model is not Asset:
@@ -311,7 +298,6 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
def check_permissions(self, request):
action_perm_require = {
"refresh": "assets.refresh_assethardwareinfo",
"test": "assets.test_assetconnectivity",
}
_action = request.data.get("action")
perm_required = action_perm_require.get(_action)

View File

@@ -2,7 +2,7 @@ from typing import List
from rest_framework.request import Request
from assets.models import Node, Platform, Protocol, MyAsset
from assets.models import Node, Platform, Protocol
from assets.utils import get_node_from_request, is_query_node_all_assets
from common.utils import lazyproperty, timeit
@@ -82,7 +82,6 @@ class SerializeToTreeNodeMixin:
data = []
root_assets_count = 0
MyAsset.set_asset_custom_value(assets, self.request.user)
for asset in assets:
platform = platform_map.get(asset.platform_id)
if not platform:

View File

@@ -1,13 +0,0 @@
# -*- coding: utf-8 -*-
#
from common.api import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from common.permissions import IsValidUser
from ..serializers import MyAssetSerializer
__all__ = ['MyAssetViewSet']
class MyAssetViewSet(CreateModelMixin, GenericViewSet):
serializer_class = MyAssetSerializer
permission_classes = (IsValidUser,)

View File

@@ -1,5 +1,3 @@
from django.db.models import Count
from django_filters import rest_framework as filters
from rest_framework import generics
from rest_framework import serializers
from rest_framework.decorators import action
@@ -7,7 +5,7 @@ from rest_framework.response import Response
from assets.const import AllTypes
from assets.models import Platform, Node, Asset, PlatformProtocol
from assets.serializers import PlatformSerializer, PlatformProtocolSerializer, PlatformListSerializer
from assets.serializers import PlatformSerializer, PlatformProtocolSerializer
from common.api import JMSModelViewSet
from common.permissions import IsValidUser
from common.serializers import GroupedChoiceSerializer
@@ -15,22 +13,13 @@ from common.serializers import GroupedChoiceSerializer
__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi', 'PlatformProtocolViewSet']
class PlatformFilter(filters.FilterSet):
name__startswith = filters.CharFilter(field_name='name', lookup_expr='istartswith')
class Meta:
model = Platform
fields = ['name', 'category', 'type']
class AssetPlatformViewSet(JMSModelViewSet):
queryset = Platform.objects.all()
serializer_classes = {
'default': PlatformSerializer,
'list': PlatformListSerializer,
'categories': GroupedChoiceSerializer,
}
filterset_class = PlatformFilter
filterset_fields = ['name', 'category', 'type']
search_fields = ['name']
ordering = ['-internal', 'name']
rbac_perms = {
@@ -42,8 +31,8 @@ class AssetPlatformViewSet(JMSModelViewSet):
def get_queryset(self):
# 因为没有走分页逻辑,所以需要这里 prefetch
queryset = super().get_queryset().annotate(assets_amount=Count('assets')).prefetch_related(
'protocols', 'automation', 'labels', 'labels__label'
queryset = super().get_queryset().prefetch_related(
'protocols', 'automation', 'labels', 'labels__label',
)
queryset = queryset.filter(type__in=AllTypes.get_types_values())
return queryset

View File

@@ -39,16 +39,16 @@ class NodeChildrenApi(generics.ListCreateAPIView):
self.instance = self.get_object()
def perform_create(self, serializer):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if value:
children = self.instance.get_children()
if children.filter(value=value).exists():
raise JMSException(_('The same level node name cannot be the same'))
else:
value = self.instance.get_next_child_preset_name()
with NodeAddChildrenLock(self.instance):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if value:
children = self.instance.get_children()
if children.filter(value=value).exists():
raise JMSException(_('The same level node name cannot be the same'))
else:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value

View File

@@ -113,7 +113,11 @@ class BasePlaybookManager:
if not data:
data = automation_params.get(method_id, {})
params = serializer(data).data
return params
return {
field_name: automation_params.get(field_name, '')
if not params[field_name] else params[field_name]
for field_name in params
}
@property
def platform_automation_methods(self):
@@ -170,7 +174,6 @@ class BasePlaybookManager:
result = self.write_cert_to_file(
os.path.join(cert_dir, f), specific.get(f)
)
os.chmod(result, 0o600)
host['jms_asset']['secret_info'][f] = result
return host

View File

@@ -3,9 +3,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -15,9 +12,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

View File

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -15,10 +11,6 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: db_info
- name: Define info by set_fact

View File

@@ -12,12 +12,7 @@
cpu_cores: "{{ ansible_processor_cores }}"
cpu_vcpus: "{{ ansible_processor_vcpus }}"
memory: "{{ ansible_memtotal_mb / 1024 | round(2) }}"
disk_total: |-
{% set ns = namespace(total=0) %}
{%- for name, dev in ansible_devices.items() if dev.removable == '0' and dev.host != '' -%}
{%- set ns.total = ns.total + ( dev.sectors | int * dev.sectorsize | int ) -%}
{%- endfor -%}
{{- (ns.total / 1024 / 1024 / 1024) | round(2) -}}
disk_total: "{{ (ansible_mounts | map(attribute='size_total') | sum / 1024 / 1024 / 1024) | round(2) }}"
distribution: "{{ ansible_distribution }}"
distribution_version: "{{ ansible_distribution_version }}"
arch: "{{ ansible_architecture }}"

View File

@@ -11,10 +11,8 @@
vendor: "{{ ansible_system_vendor }}"
model: "{{ ansible_product_name }}"
sn: "{{ ansible_product_serial }}"
cpu_count: "{{ ansible_processor_count }}"
cpu_cores: "{{ ansible_processor_cores }}"
cpu_vcpus: "{{ ansible_processor_vcpus }}"
memory: "{{ (ansible_memtotal_mb / 1024) | round(2) }}"
memory: "{{ ansible_memtotal_mb }}"
- debug:
var: info

View File

@@ -13,3 +13,4 @@
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 }}"

View File

@@ -3,9 +3,6 @@
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 }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test MySQL connection
@@ -15,7 +12,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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

@@ -2,10 +2,6 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test PostgreSQL connection
@@ -15,9 +11,5 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available

View File

@@ -115,7 +115,7 @@ class PingGatewayManager:
@staticmethod
def before_runner_start():
print(_(">>> Start executing the task to test gateway connectivity"))
print(">>> 开始执行测试网关可连接性任务")
def get_accounts(self, gateway):
account = gateway.select_account

View File

@@ -1,8 +1,6 @@
from .automation import *
from .base import *
from .category import *
from .database import *
from .host import *
from .platform import *
from .protocol import *
from .types import *

View File

@@ -112,7 +112,7 @@ class BaseType(TextChoices):
@classmethod
def get_choices(cls):
if not settings.XPACK_LICENSE_IS_VALID:
if not settings.XPACK_ENABLED:
choices = [(tp.value, tp.label) for tp in cls.get_community_types()]
else:
choices = cls.choices

View File

@@ -1,5 +1,3 @@
from django.db.models import TextChoices
from .base import BaseType
@@ -122,10 +120,3 @@ class DatabaseTypes(BaseType):
cls.MYSQL, cls.MARIADB, cls.POSTGRESQL,
cls.MONGODB, cls.REDIS,
]
class PostgresqlSSLMode(TextChoices):
PREFER = 'prefer', 'Prefer'
REQUIRE = 'require', 'Require'
VERIFY_CA = 'verify-ca', 'Verify CA'
VERIFY_FULL = 'verify-full', 'Verify Full'

View File

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

View File

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

View File

@@ -45,12 +45,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
'default': False,
'label': _('Old SSH version'),
'help_text': _('Old SSH version like openssh 5.x or 6.x')
},
'nc': {
'type': 'bool',
'default': False,
'label': 'Netcat (nc)',
'help_text': _('Netcat help text')
}
}
},
@@ -86,18 +80,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
'choices': [('any', _('Any')), ('rdp', 'RDP'), ('tls', 'TLS'), ('nla', 'NLA')],
'default': 'any',
'label': _('Security'),
'help_text': _("Security layer to use for the connection:<br>"
"Any<br>"
"Automatically select the security mode based on the security protocols "
"supported by both the client and the server<br>"
"RDP<br>"
"Legacy RDP encryption. This mode is generally only used for older Windows "
"servers or in cases where a standard Windows login screen is desired<br>"
"TLS<br>"
"RDP authentication and encryption implemented via TLS.<br>"
"NLA<br>"
"This mode uses TLS encryption and requires the username and password "
"to be given in advance")
'help_text': _("Security layer to use for the connection")
},
'ad_domain': {
'type': 'str',
@@ -225,12 +208,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
'default': 'admin',
'label': _('Auth source'),
'help_text': _('The database to authenticate against')
},
'connection_options': {
'type': 'str',
'default': '',
'label': _('Connect options'),
'help_text': _('The connection specific options eg. retryWrites=false&retryReads=false')
}
}
},
@@ -312,17 +289,23 @@ class Protocol(ChoicesMixin, models.TextChoices):
'setting': {
'api_mode': {
'type': 'choice',
'default': 'gpt-4o-mini',
'default': 'gpt-3.5-turbo',
'label': _('API mode'),
'choices': [
('gpt-4o-mini', 'GPT-4o-mini'),
('gpt-4o', 'GPT-4o'),
('gpt-4-turbo', 'GPT-4 Turbo'),
('gpt-3.5-turbo', 'GPT-3.5 Turbo'),
('gpt-3.5-turbo-1106', 'GPT-3.5 Turbo 1106'),
]
}
}
}
}
if settings.XPACK_LICENSE_IS_VALID:
choices = protocols[cls.chatgpt]['setting']['api_mode']['choices']
choices.extend([
('gpt-4', 'GPT-4'),
('gpt-4-turbo', 'GPT-4 Turbo'),
('gpt-4o', 'GPT-4o'),
])
return protocols
@classmethod

View File

@@ -3,7 +3,6 @@ from collections import defaultdict
from copy import deepcopy
from django.conf import settings
from django.utils.functional import lazy
from django.utils.translation import gettext as _
from common.db.models import ChoicesMixin
@@ -30,15 +29,15 @@ class AllTypes(ChoicesMixin):
@classmethod
def choices(cls):
return lazy(cls.get_choices, list)()
@classmethod
def get_choices(cls):
choices = []
for tp in cls.includes:
choices.extend(tp.get_choices())
return choices
@classmethod
def get_choices(cls):
return cls.choices()
@classmethod
def filter_choices(cls, category):
choices = dict(cls.category_types()).get(category, cls).get_choices()
@@ -173,7 +172,7 @@ class AllTypes(ChoicesMixin):
(Category.DATABASE, DatabaseTypes),
(Category.WEB, WebTypes),
(Category.CLOUD, CloudTypes),
(Category.CUSTOM, CustomTypes)
(Category.CUSTOM, CustomTypes),
]
return types

View File

@@ -1,35 +0,0 @@
# Generated by Django 4.1.13 on 2024-08-06 09:11
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0004_auto_20240709_1819'),
]
operations = [
migrations.CreateModel(
name='MyAsset',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(default='', max_length=128, verbose_name='Custom Name')),
('comment', models.CharField(default='', max_length=512, verbose_name='Custom Comment')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='my_assets', to='assets.asset')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'My assets',
'unique_together': {('user', 'asset')},
},
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.13 on 2024-09-13 08:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0005_myasset'),
]
operations = [
migrations.AddField(
model_name='database',
name='pg_ssl_mode',
field=models.CharField(choices=[
('prefer', 'Prefer'),
('require', 'Require'),
('verify-ca', 'Verify CA'),
('verify-full', 'Verify Full')
], default='prefer',
max_length=16, verbose_name='Postgresql SSL mode'),
),
]

View File

@@ -7,4 +7,3 @@ from .domain import *
from .node import *
from .favorite_asset import *
from .automations import *
from .my_asset import *

View File

@@ -38,13 +38,6 @@ class AssetQuerySet(models.QuerySet):
def valid(self):
return self.active()
def gateways(self, is_gateway=1):
kwargs = {'platform__name__startswith': 'Gateway'}
if is_gateway:
return self.filter(**kwargs)
else:
return self.exclude(**kwargs)
def has_protocol(self, name):
return self.filter(protocols__contains=name)
@@ -165,16 +158,10 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
name = models.CharField(max_length=128, verbose_name=_('Name'))
address = models.CharField(max_length=767, verbose_name=_('Address'), db_index=True)
platform = models.ForeignKey(
Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets'
)
domain = models.ForeignKey(
"assets.Domain", null=True, blank=True, related_name='assets',
verbose_name=_("Zone"), on_delete=models.SET_NULL
)
nodes = models.ManyToManyField(
'assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")
)
platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
verbose_name=_("Zone"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
gathered_info = models.JSONField(verbose_name=_('Gathered info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息
custom_info = models.JSONField(verbose_name=_('Custom info'), default=dict)
@@ -186,7 +173,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
def get_labels(self):
from labels.models import Label, LabeledResource
res_type = ContentType.objects.get_for_model(self.__class__.label_model())
res_type = ContentType.objects.get_for_model(self.__class__)
label_ids = LabeledResource.objects.filter(res_type=res_type, res_id=self.id) \
.values_list('label_id', flat=True)
return Label.objects.filter(id__in=label_ids)

View File

@@ -1,7 +1,6 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from assets.const import PostgresqlSSLMode
from common.db.fields import EncryptTextField
from .common import Asset
@@ -13,10 +12,6 @@ class Database(Asset):
client_cert = EncryptTextField(verbose_name=_("Client cert"), blank=True)
client_key = EncryptTextField(verbose_name=_("Client key"), blank=True)
allow_invalid_cert = models.BooleanField(default=False, verbose_name=_('Allow invalid cert'))
pg_ssl_mode = models.CharField(
max_length=16, choices=PostgresqlSSLMode.choices,
default=PostgresqlSSLMode.PREFER, verbose_name=_('Postgresql SSL mode')
)
def __str__(self):
return '{}({}://{}/{})'.format(self.name, self.type, self.address, self.db_name)

View File

@@ -31,7 +31,7 @@ class Domain(LabeledMixin, JMSOrgBaseModel):
@lazyproperty
def assets_amount(self):
return self.assets.gateways(0).count()
return self.assets.count()
def random_gateway(self):
gateways = [gw for gw in self.active_gateways if gw.is_connective]

View File

@@ -16,7 +16,7 @@ __all__ = ['Gateway']
class GatewayManager(OrgManager):
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(platform__name__startswith=GATEWAY_NAME)
queryset = queryset.filter(platform__name=GATEWAY_NAME)
return queryset
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
@@ -33,6 +33,10 @@ class Gateway(Host):
proxy = True
verbose_name = _("Gateway")
def save(self, *args, **kwargs):
self.platform = self.default_platform()
return super().save(*args, **kwargs)
@classmethod
def default_platform(cls):
return Platform.objects.get(name=GATEWAY_NAME, internal=True)

View File

@@ -1,43 +0,0 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
__all__ = ['MyAsset']
class MyAsset(JMSBaseModel):
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, related_name='my_assets')
name = models.CharField(verbose_name=_("Custom Name"), max_length=128, default='')
comment = models.CharField(verbose_name=_("Custom Comment"), max_length=512, default='')
custom_fields = ['name', 'comment']
class Meta:
unique_together = ('user', 'asset')
verbose_name = _("My assets")
def custom_to_dict(self):
data = {}
for field in self.custom_fields:
value = getattr(self, field)
if value == "":
continue
data.update({field: value})
return data
@staticmethod
def set_asset_custom_value(assets, user):
my_assets = MyAsset.objects.filter(asset__in=assets, user=user).all()
customs = {my_asset.asset.id: my_asset.custom_to_dict() for my_asset in my_assets}
for asset in assets:
custom = customs.get(asset.id)
if not custom:
continue
for field, value in custom.items():
if not hasattr(asset, field):
continue
setattr(asset, field, value)
def __str__(self):
return f'{self.user}-{self.asset}'

View File

@@ -1,7 +1,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from assets.const import AllTypes, Category, Protocol, SuMethodChoices
from assets.const import AllTypes, Category, Protocol
from common.db.fields import JsonDictTextField
from common.db.models import JMSBaseModel
@@ -111,10 +111,6 @@ class Platform(LabeledMixin, JMSBaseModel):
def type_constraints(self):
return AllTypes.get_constraints(self.category, self.type)
@lazyproperty
def assets_amount(self):
return self.assets.count()
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
@@ -131,17 +127,6 @@ class Platform(LabeledMixin, JMSBaseModel):
return True
return False
@property
def ansible_become_method(self):
su_method = self.su_method or SuMethodChoices.sudo
if su_method in [SuMethodChoices.sudo, SuMethodChoices.only_sudo]:
method = SuMethodChoices.sudo
elif su_method in [SuMethodChoices.su, SuMethodChoices.only_su]:
method = SuMethodChoices.su
else:
method = su_method
return method
def __str__(self):
return self.name

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