Compare commits

..

34 Commits

Author SHA1 Message Date
老广
3533c01011 Update requirements.txt 2017-12-14 12:03:32 +08:00
老广
77ec2d5a86 Update connect.py
解决 index error的问题
2017-12-04 16:14:33 +08:00
ibuler
8b707dc8f5 修改文案说明 2017-10-09 20:32:45 +08:00
老广
8dcc9c8d53 Update web_terminal.html 2017-10-09 05:45:19 -05:00
老广
0ca0519ef1 Update install.py 2017-09-11 09:38:58 +08:00
老广
8de50a0e83 Update README.md 2017-09-08 07:19:16 +08:00
假想控
64b7fb6a3d 添加系统用户推送,使用密钥说明文案
添加系统用户推送,使用密钥说明文案
2017-09-02 18:31:41 +08:00
vincent927
a114e173e0 修复centos7安装报错-平台不支持 (#603) 2017-08-07 16:44:11 +08:00
Leon Chan
402df81048 make sure logs directory exists (#374) 2017-04-20 16:51:52 +08:00
老广
2a51eeef10 Update connect.py 2017-03-13 18:30:23 +08:00
假想控
4b593e56f9 添加系统用户推送,使用密钥说明文案
添加系统用户推送,使用密钥说明文案
2016-11-22 17:44:20 +08:00
紫川秀
9f3c83b052 修复普通用户能添加授权bug (#324)
* 修复sudo别名取消后不回收bug

* 修复普通用户能添加授权bug
2016-11-17 10:54:23 +08:00
紫川秀
a864c38238 修复sudo别名取消后不回收bug (#322) 2016-11-16 10:10:04 +08:00
假想控
547408222d 网页连接主机 js regx bug #292
ws://jumpserver.com/ws/terminal?id=1&role=admin,?id=1&role=admin
而期望的结果应该是ws://jumpserver.com/ws/terminal?id=1&role=admin
所以document.URL.match(/(\?.*)/)里的正则表达式应该去掉分组,变为 document.URL.match(/\?.*/)
2016-11-09 13:39:45 +08:00
假想控
0ad26254ef 修改系统用户推送失败, 进入系统用户详情说明文案
修改系统用户推送失败, 进入系统用户详情说明文案
2016-11-09 12:34:26 +08:00
假想控
b7e138f919 修改添加系统用户,用户名说明文案
修改添加系统用户,用户名说明文案
2016-11-09 11:28:16 +08:00
假想控
2ec415fd84 修改添加系统用户,密码说明文案
修改添加系统用户,密码说明文案
2016-11-09 10:59:34 +08:00
假想控
b3c12e8861 修复使用mysql未初始化问题
1.修复使用mysql未初始化问题
2.crontab add
2016-11-07 13:39:12 +08:00
假想控
da8f30fc72 修改用户密码说明文案
修改用户密码说明文案
2016-11-04 21:07:21 +08:00
假想控
cddb0ce537 Merge pull request #310 from MoonTails83/patch-1
Update install.py
2016-11-04 20:00:58 +08:00
peter17919
124e26fa32 fix #311 (#313)
* Update views.py

* Update views.py

* Update views.py

* Update views.py

* Update views.py
2016-11-03 18:33:38 +08:00
假想控
59c689cd61 Update run.sh
1.更改sed -i "s/__MYSQL_ENGINE__/${MYSQL_ENGINE}/" /jumpserver/jumpserver.conf
2.新增MAIL_USE_SSL
2016-11-03 14:33:16 +08:00
假想控
0cc36b1302 Update docker-compose.yaml
# environment:
- USE_MAIL=true
2016-11-03 14:14:10 +08:00
假想控
0ac9d19252 修改 [db]&[mail]
1.[db]
engine = __MYSQL_ENGINE__
database = __MYSQL_NAME__
2.[mail]
email_use_ssl = __MAIL_USE_SSL__
2016-11-03 14:03:52 +08:00
假想控
d9e497e8be 修改 environment
修改 environment: 
      - USE_MYSQL=true
      - MYSQL_ENGINE=mysql
      - MYSQL_HOST=192.168.50.143
      - MYSQL_PORT=3306
      - MYSQL_USER=jumpserver
      - MYSQL_PASS=love1314
      - MYSQL_NAME=jumpserver
      - USE_ENABLED=true
      - MAIL_ENABLED=1
      - MAIL_HOST=smtp.163.com
      - MAIL_PORT=25
      - MAIL_USER=evilsmall@163.com
      - MAIL_PASS=Fuck123
      - MAIL_USE_TLS=False
      - MAIL_USE_SSL=False
2016-11-03 13:59:21 +08:00
假想控
e18ae7f82c Update user_add.html
禁止创建root用户
2016-10-27 17:53:43 +08:00
老广
e5286686b9 Update views.py
check valid user when username == 'root'
2016-10-27 17:08:46 +08:00
MoonTails83
332316b0af Update install.py 2016-10-09 11:25:25 +08:00
老广
8f666785d2 Modify select pop stdin
#283  More see this issue
2016-09-05 19:05:12 +08:00
假想控
0ed4b84b63 Update setting.html
修改管理用户说明将服务器改为客户端
2016-08-08 16:03:05 +08:00
老广
97591e6f03 Update service.sh 2016-08-05 13:39:34 +08:00
老广
5d5d8ab32a run service.sh using root user 2016-08-04 10:23:57 +08:00
老广
5489f3ae36 Modify server_add_user function
Change adduser to useradd.  Make compatible will debian, ubuntu
2016-08-04 10:08:46 +08:00
liuzheng712
ce7a3d1f33 change uri /kill to /ws/kill 2016-07-31 10:29:17 +08:00
1233 changed files with 48966 additions and 106656 deletions

View File

@@ -1,9 +0,0 @@
.git
logs/*
data/*
.github
tmp/*
django.db
celerybeat.pid
### Vagrant ###
.vagrant/

View File

@@ -1,17 +0,0 @@
[简述你的问题]
##### 使用版本
[请提供你使用的JumpServer版本 如 2.0.1 注: 1.4及以下版本不再提供支持]
##### 问题复现步骤
1. [步骤1]
2. [步骤2]
##### 具体表现[截图可能会更好些,最好能截全]
##### 其他
[注:] 完成后请关闭 issue

View File

@@ -1,44 +0,0 @@
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '🌱 新功能 Features'
labels:
- 'feature'
- 'enhancement'
- 'feat'
- '新功能'
- title: '🚀 性能优化 Optimization'
labels:
- 'perf'
- 'opt'
- 'refactor'
- 'Optimization'
- '优化'
- title: '🐛 Bug修复 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 其它 Maintenance'
labels:
- 'chore'
- 'docs'
exclude-labels:
- 'no'
- '无需处理'
- 'wontfix'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## 版本变化 Whats Changed
$CHANGES

View File

@@ -1,46 +0,0 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Create Release And Upload assets
jobs:
create-realese:
name: Create Release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Get version
id: get_version
run: |
TAG=$(basename ${GITHUB_REF})
VERSION=${TAG/v/}
echo "::set-output name=TAG::$TAG"
echo "::set-output name=VERSION::$VERSION"
- name: Create Release
id: create_release
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
config-name: release-config.yml
version: ${{ steps.get_version.outputs.TAG }}
tag: ${{ steps.get_version.outputs.TAG }}
build-and-release:
needs: create-realese
name: Build and Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build it and upload
uses: jumpserver/action-build-upload-assets@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-realese.outputs.upload_url }}

74
.gitignore vendored
View File

@@ -1,40 +1,46 @@
*.py[cod]
.idea
test.py
.DS_Store
*.pyc
*.pyo
*.swp
.env
env
env*
venv
dist
build
db.sqlite3
# C extensions
*.so
# Packages
*.egg
*.egg-info
_mailinglist
dump.rdb
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
__pycache__
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
.cache/
.idea/
db.sqlite3
config.py
config.yml
nosetests.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.settings
*.log
host_rsa_key
*.bat
tags
jumpserver.iml
.python-version
tmp/*
sessions/*
media
celerybeat.pid
django.db
celerybeat-schedule.db
data/static
docs/_build/
xpack
logs/*
### Vagrant ###
.vagrant/
release/*
releashe
keys/*
jumpserver.conf
nohup.out
tmp/*

View File

@@ -1,26 +1,37 @@
FROM registry.fit2cloud.com/public/python:v3
MAINTAINER Jumpserver Team <ibuler@qq.com>
WORKDIR /opt/jumpserver
RUN useradd jumpserver
COPY ./requirements /tmp/requirements
RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && pip install wheel && \
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt || pip install -r requirements.txt
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
COPY . /opt/jumpserver
RUN echo > config.yml
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
EXPOSE 8070
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]
FROM alpine
MAINTAINER xRain <xrain@simcu.com>
RUN apk add --update openssh sshpass python py-mysqldb py-psutil py-crypto && \
rm -rf /var/cache/apk/*
COPY . /jumpserver
WORKDIR /jumpserver
RUN python /jumpserver/install/docker/get-pip.py && \
pip install -r /jumpserver/install/docker/piprequires.txt && \
rm -rf /jumpserver/docs && \
cp /jumpserver/install/docker/run.sh /run.sh && \
rm -rf /etc/motd && chmod +x /run.sh && \
rm -rf /jumpserver/keys && \
rm -rf /jumpserver/logs && \
rm -rf /home && \
rm -rf /etc/ssh && \
rm -rf /etc/shadow && \
rm -rf /etc/passwd && \
cp -r /jumpserver/install/docker/useradd /usr/sbin/useradd && \
cp -r /jumpserver/install/docker/userdel /usr/sbin/userdel && \
chmod +x /usr/sbin/useradd && \
chmod +x /usr/sbin/userdel && \
mkdir -p /data/home && \
mkdir -p /data/logs && \
mkdir -p /data/keys && \
mkdir -p /data/ssh && \
cp -r /jumpserver/install/docker/shadow /data/shadow && \
cp -r /jumpserver/install/docker/passwd /data/passwd && \
ln -s /data/logs /jumpserver/logs && \
ln -s /data/keys /jumpserver/keys && \
ln -s /data/home /home && \
ln -s /data/ssh /etc/ssh && \
ln -s /data/passwd /etc/passwd && \
ln -s /data/shadow /etc/shadow && \
chmod -R 777 /jumpserver
VOLUME /data
EXPOSE 80 22
CMD /run.sh

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
@@ -336,4 +336,4 @@ This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Public License instead of this License.

262
README.md
View File

@@ -1,234 +1,88 @@
# JumpServer 多云环境下更好用的堡垒机
## 写在前面
- 版本号变更 2.0 -> 0.2版本 3.0 -> 0.3版本
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-2.2-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Docker Pulls](https://img.shields.io/docker/pulls/jumpserver/jms_all.svg)](https://hub.docker.com/u/jumpserver)
#欢迎使用Jumpserver
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。
支持常见系统:
1. CentOS, RedHat, Fedora, Amazon Linux
2. Debian
3. SUSE, Ubuntu
4. FreeBSD
5. 其他ssh协议硬件设备
JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。
###截图:
JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
首页
![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/index.jpg)
JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
WebTerminal:
改变世界,从一点点开始。
![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/webTerminal.gif)
> 注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 JumpServer 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
Web批量执行命令
## 特色优势
![WebExecCommand](https://github.com/ibuler/static/raw/master/jumpserver3/webExec.gif)
- 开源: 零门槛,线上快速获取和安装, 修复版本视情况而定;
, 修复版本视情况而定- 分布式: 轻松支持大规模并发访问;
- 无插件: 仅需浏览器,极致的 Web Terminal 使用体验;
- 多云支持: 一套系统,同时管理不同云上面的资产;
- 云端存储: 审计录像云端存储,永不丢失;
- 多租户: 一套系统,多个子公司和部门同时使用。
录像回放
## 版本说明
![录像](https://github.com/ibuler/static/raw/master/jumpserver3/record.gif)
自 v2.0.0 发布后, JumpServer 版本号命名将变更为v大版本.功能版本.Bug修复版本。比如
跳转和批量命令
```
v2.0.1 是 v2.0.0 之后的Bug修复版本
v2.1.0 是 v2.0.0 之后的功能版本。
```
![跳转](https://github.com/ibuler/static/raw/master/jumpserver3/connect.gif)
像其它优秀开源项目一样JumpServer 每个月会发布一个功能版本,并同时维护 3 个功能版本。比如:
命令统计
```
在 v2.4 发布前,我们会同时维护 v2.1、v2.2、v2.3
在 v2.4 发布后,我们会同时维护 v2.2、v2.3、v2.4v2.1 会停止维护。
```
![跳转](https://github.com/ibuler/static/raw/master/jumpserver3/command.jpg)
## 功能列表
### 文档
<table>
<tr>
<td rowspan="8">身份认证<br>Authentication</td>
<td rowspan="5">登录认证</td>
<td>资源统一登录与认证</td>
</tr>
<tr>
<td>LDAP/AD 认证</td>
</tr>
<tr>
<td>RADIUS 认证</td>
</tr>
<tr>
<td>OpenID 认证(实现单点登录)</td>
</tr>
<tr>
<td>CAS 认证 (实现单点登录)</td>
</tr>
<tr>
<td rowspan="2">MFA认证</td>
<td>MFA 二次认证Google Authenticator</td>
</tr>
<tr>
<td>RADIUS 二次认证</td>
</tr>
<tr>
<td>登录复核X-PACK</td>
<td>用户登录行为受管理员的监管与控制</td>
</tr>
<tr>
<td rowspan="11">账号管理<br>Account</td>
<td rowspan="2">集中账号</td>
<td>管理用户管理</td>
</tr>
<tr>
<td>系统用户管理</td>
</tr>
<tr>
<td rowspan="4">统一密码</td>
<td>资产密码托管</td>
</tr>
<tr>
<td>自动生成密码</td>
</tr>
<tr>
<td>自动推送密码</td>
</tr>
<tr>
<td>密码过期设置</td>
</tr>
<tr>
<td rowspan="2">批量改密X-PACK</td>
<td>定期批量改密</td>
</tr>
<tr>
<td>多种密码策略</td>
</tr>
<tr>
<td>多云纳管X-PACK</td>
<td>对私有云、公有云资产自动统一纳管</td>
</tr>
<tr>
<td>收集用户X-PACK</td>
<td>自定义任务定期收集主机用户</td>
</tr>
<tr>
<td>密码匣子X-PACK</td>
<td>统一对资产主机的用户密码进行查看、更新、测试操作</td>
</tr>
<tr>
<td rowspan="15">授权控制<br>Authorization</td>
<td>多维授权</td>
<td>对用户、用户组、资产、资产节点、应用以及系统用户进行授权</td>
</tr>
<tr>
<td rowspan="4">资产授权</td>
<td>资产以树状结构进行展示</td>
</tr>
<tr>
<td>资产和节点均可灵活授权</td>
</tr>
<tr>
<td>节点内资产自动继承授权</td>
</tr>
<tr>
<td>子节点自动继承父节点授权</td>
</tr>
<tr>
<td rowspan="2">应用授权</td>
<td>实现更细粒度的应用级授权</td>
</tr>
<tr>
<td>MySQL 数据库应用、RemoteApp 远程应用X-PACK</td>
</tr>
<tr>
<td>动作授权</td>
<td>实现对授权资产的文件上传、下载以及连接动作的控制</td>
</tr>
<tr>
<td>时间授权</td>
<td>实现对授权资源使用时间段的限制</td>
</tr>
<tr>
<td>特权指令</td>
<td>实现对特权指令的使用(支持黑白名单)</td>
</tr>
<tr>
<td>命令过滤</td>
<td>实现对授权系统用户所执行的命令进行控制</td>
</tr>
<tr>
<td>文件传输</td>
<td>SFTP 文件上传/下载</td>
</tr>
<tr>
<td>文件管理</td>
<td>实现 Web SFTP 文件管理</td>
</tr>
<tr>
<td>工单管理X-PACK</td>
<td>支持对用户登录请求行为进行控制</td>
</tr>
<tr>
<td>组织管理X-PACK</td>
<td>实现多租户管理与权限隔离</td>
</tr>
<tr>
<td rowspan="7">安全审计<br>Audit</td>
<td>操作审计</td>
<td>用户操作行为审计</td>
</tr>
<tr>
<td rowspan="2">会话审计</td>
<td>在线会话内容审计</td>
</tr>
<tr>
<td>历史会话内容审计</td>
</tr>
<tr>
<td rowspan="2">录像审计</td>
<td>支持对 Linux、Windows 等资产操作的录像进行回放审计</td>
</tr>
<tr>
<td>支持对 RemoteAppX-PACK、MySQL 等应用操作的录像进行回放审计</td>
</tr>
<tr>
<td>指令审计</td>
<td>支持对资产和应用等操作的命令进行审计</td>
</tr>
<tr>
<td>文件传输</td>
<td>可对文件的上传、下载记录进行审计</td>
</tr>
</table>
* [访问wiki](https://github.com/jumpserver/jumpserver/wiki)
* [概览](https://github.com/jumpserver/jumpserver/wiki/%E6%A6%82%E8%A7%88)
* [名词解释](https://github.com/jumpserver/jumpserver/wiki/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A)
* [常见问题](https://github.com/jumpserver/jumpserver/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
* 安装基于:[RedHat 的系统](https://github.com/jumpserver/jumpserver/wiki/%E5%9F%BA%E4%BA%8E-RedHat-%E7%9A%84%E7%B3%BB%E7%BB%9F)[Debian 的系统](https://github.com/jumpserver/jumpserver/wiki/%E5%9F%BA%E4%BA%8E-Debian-%E7%9A%84%E7%B3%BB%E7%BB%9F)
* [快速开始](https://github.com/jumpserver/jumpserver/wiki/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)
* [安装图解](https://github.com/jumpserver/jumpserver/wiki/%E5%AE%89%E8%A3%85%E5%9B%BE%E8%A7%A3)
* [应用图解](https://github.com/jumpserver/jumpserver/wiki/%E5%BA%94%E7%94%A8%E5%9B%BE%E8%A7%A3)
## 快速开始
### 特点
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
- [完整文档](https://docs.jumpserver.org)
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
* 完全开源GPL授权
* Python编写容易再次开发
* 实现了跳板机基本功能,认证、授权、审计
* 集成了Ansible批量命令等
* 支持WebTerminal
* Bootstrap编写界面美观
* 自动收集硬件信息
* 录像回放
* 命令搜索
* 实时监控
* 批量上传下载
## 案例研究
### 其它
- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
- [中通快递JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。
[Jumpserver官网](http://www.jumpserver.org)
## 安全说明
[论坛](http://bbs.jumpserver.org)
JumpServer是一款安全产品请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/) 部署安装.
[demo站点](http://demo.jumpserver.org)
如果你发现安全问题,可以直接联系我们:
交流群: 552054376
- ibuler@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
### 团队
## License & Copyright
![](https://github.com/ibuler/static/raw/master/jumpserver3/team.jpg)
Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
### License & Copyright
Copyright (c) 2014-2017 Beijing Duizhan Tech, Inc., All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.gnu.org/licenses/gpl-2.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.

View File

@@ -1,60 +0,0 @@
## Jumpserver
![Total visitor](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=jumpserver)
![Visitors in today](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=jumpserver)
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-2.1-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/)
----
- [中文版](https://github.com/jumpserver/jumpserver/blob/master/README_EN.md)
Jumpserver is the first fully open source bastion in the world, based on the GNU GPL v2.0 open source protocol. Jumpserver is a professional operation and maintenance audit system conforms to 4A specifications.
Jumpserver is developed using Python / Django, conforms to the Web 2.0 specification, and is equipped with the industry-leading Web Terminal solution which have beautiful interface and great user experience.
Jumpserver adopts a distributed architecture to support multi-branch deployment across multiple areas. The central node provides APIs, and login nodes are deployed in each branch. It can be scaled horizontally without concurrency restrictions.
Change the world, starting from little things.
----
### Features
![Jumpserver 功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver148.jpeg "Jumpserver 功能")
### Start
Quick start [Docker Install](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
Step by Step deployment. [Docs](http://docs.jumpserver.org/zh/docs/step_by_step.html)
Full documentation [Docs](http://docs.jumpserver.org)
### Demo、Video 和 Snapshot
We provide online demo, demo video and screenshots to get you started quickly.
[Demo](https://demo.jumpserver.org/auth/login/?next=/)
[Video](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
[Snapshot](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
We provide the SDK for your other systems to quickly interact with the Jumpserver API.
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver other components use this SDK to complete the interaction.
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK thanks to 恺珺 for provide Java SDK
### License & Copyright
Copyright (c) 2014-2019 Beijing Duizhan Tech, Inc., All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.gnu.org/licenses/gpl-2.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.

56
Vagrantfile vendored
View File

@@ -1,56 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box_check_update = false
config.vm.box = "centos/7"
config.vm.hostname = "jumpserver"
config.vm.network "private_network", ip: "172.17.8.101"
config.vm.provider "virtualbox" do |vb|
vb.memory = "4096"
vb.cpus = 2
vb.name = "jumpserver"
end
config.vm.synced_folder ".", "/vagrant", type: "rsync",
rsync__verbose: true,
rsync__exclude: ['.git*', 'node_modules*','*.log','*.box','Vagrantfile']
config.vm.provision "shell", inline: <<-SHELL
## 设置yum的阿里云源
sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
sudo sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
sudo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
sudo yum makecache
## 安装依赖包
sudo yum install -y python36 python36-devel python36-pip \
libtiff-devel libjpeg-devel libzip-devel freetype-devel \
lcms2-devel libwebp-devel tcl-devel tk-devel sshpass \
openldap-devel mariadb-devel mysql-devel libffi-devel \
openssh-clients telnet openldap-clients gcc
## 配置pip阿里云源
mkdir /home/vagrant/.pip
cat << EOF | sudo tee -a /home/vagrant/.pip/pip.conf
[global]
timeout = 6000
index-url = https://mirrors.aliyun.com/pypi/simple/
[install]
use-mirrors = true
mirrors = https://mirrors.aliyun.com/pypi/simple/
trusted-host=mirrors.aliyun.com
EOF
python3.6 -m venv /home/vagrant/venv
source /home/vagrant/venv/bin/activate
echo 'source /home/vagrant/venv/bin/activate' >> /home/vagrant/.bash_profile
SHELL
end

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#

View File

@@ -1,2 +0,0 @@
from .remote_app import *
from .database_app import *

View File

@@ -1,20 +0,0 @@
# coding: utf-8
#
from orgs.mixins.api import OrgBulkModelViewSet
from .. import models
from .. import serializers
from ..hands import IsOrgAdminOrAppUser
__all__ = [
'DatabaseAppViewSet',
]
class DatabaseAppViewSet(OrgBulkModelViewSet):
model = models.DatabaseApp
filter_fields = ('name',)
search_fields = filter_fields
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DatabaseAppSerializer

View File

@@ -1,27 +0,0 @@
# coding: utf-8
#
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
__all__ = [
'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
]
class RemoteAppViewSet(OrgBulkModelViewSet):
model = RemoteApp
filter_fields = ('name', 'type', 'comment')
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppSerializer
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
model = RemoteApp
permission_classes = (IsAppUser, )
serializer_class = RemoteAppConnectionInfoSerializer

View File

@@ -1,7 +0,0 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class ApplicationsConfig(AppConfig):
name = 'applications'

View File

@@ -1,63 +0,0 @@
# coding: utf-8
#
from django.utils.translation import ugettext_lazy as _
# RemoteApp
REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
REMOTE_APP_TYPE_CHROME = 'chrome'
REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench'
REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client'
REMOTE_APP_TYPE_CUSTOM = 'custom'
# Fields attribute write_only default => False
REMOTE_APP_TYPE_CHROME_FIELDS = [
{'name': 'chrome_target'},
{'name': 'chrome_username'},
{'name': 'chrome_password', 'write_only': True}
]
REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [
{'name': 'mysql_workbench_ip'},
{'name': 'mysql_workbench_name'},
{'name': 'mysql_workbench_username'},
{'name': 'mysql_workbench_password', 'write_only': True}
]
REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [
{'name': 'vmware_target'},
{'name': 'vmware_username'},
{'name': 'vmware_password', 'write_only': True}
]
REMOTE_APP_TYPE_CUSTOM_FIELDS = [
{'name': 'custom_cmdline'},
{'name': 'custom_target'},
{'name': 'custom_username'},
{'name': 'custom_password', 'write_only': True}
]
REMOTE_APP_TYPE_FIELDS_MAP = {
REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
}
REMOTE_APP_TYPE_CHOICES = (
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'),
(REMOTE_APP_TYPE_CUSTOM, _('Custom')),
)
# DatabaseApp
DATABASE_APP_TYPE_MYSQL = 'mysql'
DATABASE_APP_TYPE_CHOICES = (
(DATABASE_APP_TYPE_MYSQL, 'MySQL'),
)

View File

@@ -1,15 +0,0 @@
"""
jumpserver.__app__.hands.py
~~~~~~~~~~~~~~~~~
This app depends other apps api, function .. should be import or write mack here.
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2018 by JumpServer Team.
:license: GPL v2, see LICENSE for more details.
"""
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup

View File

@@ -1,42 +0,0 @@
# Generated by Django 2.1.7 on 2019-05-20 11:04
import common.fields.model
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('assets', '0026_auto_20190325_2035'),
]
operations = [
migrations.CreateModel(
name='RemoteApp',
fields=[
('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)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
('path', models.CharField(max_length=128, verbose_name='App path')),
('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
],
options={
'verbose_name': 'RemoteApp',
'ordering': ('name',),
},
),
migrations.AlterUniqueTogether(
name='remoteapp',
unique_together={('org_id', 'name')},
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.1.7 on 2019-09-09 09:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('applications', '0001_initial'),
('perms', '0009_remoteapppermission_system_users'),
]
operations = [
migrations.RemoveField(
model_name='remoteapp',
name='system_user',
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.1.11 on 2019-12-10 08:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('applications', '0002_remove_remoteapp_system_user'),
]
operations = [
migrations.AlterField(
model_name='remoteapp',
name='type',
field=models.CharField(choices=[('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type'),
),
]

View File

@@ -1,38 +0,0 @@
# Generated by Django 2.1.11 on 2019-12-18 09:05
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('applications', '0003_auto_20191210_1659'),
]
operations = [
migrations.CreateModel(
name='DatabaseApp',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created 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(max_length=128, verbose_name='Name')),
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=128, verbose_name='Type')),
('host', models.CharField(db_index=True, max_length=128, verbose_name='Host')),
('port', models.IntegerField(default=3306, verbose_name='Port')),
('database', models.CharField(blank=True, db_index=True, max_length=128, null=True, verbose_name='Database')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
],
options={
'verbose_name': 'DatabaseApp',
'ordering': ('name',),
},
),
migrations.AlterUniqueTogether(
name='databaseapp',
unique_together={('org_id', 'name')},
),
]

View File

@@ -1,2 +0,0 @@
from .remote_app import *
from .database_app import *

View File

@@ -1,42 +0,0 @@
# coding: utf-8
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from .. import const
__all__ = ['DatabaseApp']
class DatabaseApp(CommonModelMixin, OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
type = models.CharField(
default=const.DATABASE_APP_TYPE_MYSQL,
choices=const.DATABASE_APP_TYPE_CHOICES,
max_length=128, verbose_name=_('Type')
)
host = models.CharField(
max_length=128, verbose_name=_('Host'), db_index=True
)
port = models.IntegerField(default=3306, verbose_name=_('Port'))
database = models.CharField(
max_length=128, blank=True, null=True, verbose_name=_('Database'),
db_index=True
)
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
def __str__(self):
return self.name
class Meta:
unique_together = [('org_id', 'name'), ]
verbose_name = _("DatabaseApp")
ordering = ('name', )

View File

@@ -1,78 +0,0 @@
# coding: utf-8
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from common.fields.model import EncryptJsonDictTextField
from .. import const
__all__ = [
'RemoteApp',
]
class RemoteApp(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
asset = models.ForeignKey(
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
)
type = models.CharField(
default=const.REMOTE_APP_TYPE_CHROME,
choices=const.REMOTE_APP_TYPE_CHOICES,
max_length=128, verbose_name=_('App type')
)
path = models.CharField(
max_length=128, blank=False, null=False,
verbose_name=_('App path')
)
params = EncryptJsonDictTextField(
max_length=4096, default={}, blank=True, null=True,
verbose_name=_('Parameters')
)
created_by = models.CharField(
max_length=32, null=True, blank=True, verbose_name=_('Created by')
)
date_created = models.DateTimeField(
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
)
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
class Meta:
verbose_name = _("RemoteApp")
unique_together = [('org_id', 'name')]
ordering = ('name', )
def __str__(self):
return self.name
@property
def parameters(self):
"""
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
"""
_parameters = list()
_parameters.append(self.type)
path = '\"%s\"' % self.path
_parameters.append(path)
for field in const.REMOTE_APP_TYPE_FIELDS_MAP[self.type]:
value = self.params.get(field['name'])
if value is None:
continue
_parameters.append(value)
_parameters = ' '.join(_parameters)
return _parameters
@property
def asset_info(self):
return {
'id': self.asset.id,
'hostname': self.asset.hostname
}

View File

@@ -1,2 +0,0 @@
from .remote_app import *
from .database_app import *

View File

@@ -1,26 +0,0 @@
# coding: utf-8
#
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer
from .. import models
__all__ = [
'DatabaseAppSerializer',
]
class DatabaseAppSerializer(BulkOrgResourceModelSerializer):
class Meta:
model = models.DatabaseApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'type', 'get_type_display', 'host', 'port',
'database', 'comment', 'created_by', 'date_created', 'date_updated',
]
read_only_fields = [
'created_by', 'date_created', 'date_updated'
'get_type_display',
]

View File

@@ -1,86 +0,0 @@
# coding: utf-8
#
import copy
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from common.fields.serializer import CustomMetaDictField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .. import const
from ..models import RemoteApp
__all__ = [
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
]
class RemoteAppParamsDictField(CustomMetaDictField):
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
default_type = const.REMOTE_APP_TYPE_CHROME
convert_key_remove_type_prefix = False
convert_key_to_upper = False
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
params = RemoteAppParamsDictField()
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
class Meta:
model = RemoteApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'asset', 'asset_info', 'type', 'get_type_display',
'path', 'params', 'date_created', 'created_by', 'comment',
]
read_only_fields = [
'created_by', 'date_created', 'asset_info',
'get_type_display'
]
def process_params(self, instance, validated_data):
new_params = copy.deepcopy(validated_data.get('params', {}))
tp = validated_data.get('type', '')
if tp != instance.type:
return new_params
old_params = instance.params
fields = self.type_fields_map.get(instance.type, [])
for field in fields:
if not field.get('write_only', False):
continue
field_name = field['name']
new_value = new_params.get(field_name, '')
old_value = old_params.get(field_name, '')
field_value = new_value if new_value else old_value
new_params[field_name] = field_value
return new_params
def update(self, instance, validated_data):
params = self.process_params(instance, validated_data)
validated_data['params'] = params
return super().update(instance, validated_data)
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
parameter_remote_app = serializers.SerializerMethodField()
class Meta:
model = RemoteApp
fields = [
'id', 'name', 'asset', 'parameter_remote_app',
]
read_only_fields = ['parameter_remote_app']
@staticmethod
def get_parameter_remote_app(obj):
parameter = {
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
'working_directory': '',
'parameters': obj.parameters,
}
return parameter

View File

@@ -1,7 +0,0 @@
# coding: utf-8
#
__all__ = [
]

View File

@@ -1,24 +0,0 @@
# coding:utf-8
#
from django.urls import path, re_path
from rest_framework_bulk.routes import BulkRouter
from common import api as capi
from .. import api
app_name = 'applications'
router = BulkRouter()
router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
router.register(r'database-apps', api.DatabaseAppViewSet, 'database-app')
urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
]
old_version_urlpatterns = [
re_path('(?P<resource>remote-app)/.*', capi.redirect_plural_name_api)
]
urlpatterns += router.urls + old_version_urlpatterns

View File

@@ -1,7 +0,0 @@
# coding:utf-8
from django.urls import path
app_name = 'applications'
urlpatterns = [
]

View File

@@ -1,11 +0,0 @@
from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .system_user_relation import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *
from .gathered_user import *
from .favorite_asset import *

View File

@@ -1,106 +0,0 @@
from django.db import transaction
from django.db.models import Count
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework import status
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
from .. import serializers
from ..tasks import test_admin_user_connectivity_manual
logger = get_logger(__file__)
__all__ = [
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
'AdminUserAssetsListView',
]
class AdminUserViewSet(OrgBulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
model = AdminUser
filter_fields = ("name", "username")
search_fields = filter_fields
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(assets_amount=Count('assets'))
return queryset
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
has_related_asset = instance.assets.exists()
if has_related_asset:
data = {'msg': _('Deleted failed, There are related assets')}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
return super().destroy(request, *args, **kwargs)
class AdminUserAuthApi(generics.UpdateAPIView):
model = AdminUser
serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
model = AdminUser
serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsOrgAdmin,)
def update(self, request, *args, **kwargs):
admin_user = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
nodes = serializer.validated_data['nodes']
assets = []
for node in nodes:
assets.extend([asset.id for asset in node.get_all_assets()])
with transaction.atomic():
Asset.objects.filter(id__in=assets).update(admin_user=admin_user)
return Response({"msg": "ok"})
else:
return Response({'error': serializer.errors}, status=400)
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset admin user assets_connectivity
"""
model = AdminUser
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
def retrieve(self, request, *args, **kwargs):
admin_user = self.get_object()
task = test_admin_user_connectivity_manual.delay(admin_user)
return Response({"task": task.id})
class AdminUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer
filter_fields = ("hostname", "ip")
http_method_names = ['get']
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(AdminUser, pk=pk)
def get_queryset(self):
admin_user = self.get_object()
return admin_user.get_related_assets()

View File

@@ -1,126 +0,0 @@
# -*- coding: utf-8 -*-
#
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import RetrieveAPIView
from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..models import Asset, Node, Platform
from .. import serializers
from ..tasks import (
update_asset_hardware_info_manual, test_asset_connectivity_manual
)
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'AssetPlatformRetrieveApi',
'AssetGatewayListApi', 'AssetPlatformViewSet',
'AssetTaskCreateApi',
]
class AssetViewSet(OrgBulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
model = Asset
filter_fields = (
"hostname", "ip", "systemuser__id", "admin_user__id", "platform__base",
"is_active"
)
search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
serializer_classes = {
'default': serializers.AssetSerializer,
'display': serializers.AssetDisplaySerializer,
}
permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
def set_assets_node(self, assets):
if not isinstance(assets, list):
assets = [assets]
node_id = self.request.query_params.get('node_id')
if not node_id:
return
node = get_object_or_none(Node, pk=node_id)
if not node:
return
node.assets.add(*assets)
def perform_create(self, serializer):
assets = serializer.save()
self.set_assets_node(assets)
class AssetPlatformRetrieveApi(RetrieveAPIView):
queryset = Platform.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.PlatformSerializer
def get_object(self):
asset_pk = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_pk)
return asset.platform
class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.PlatformSerializer
filter_fields = ['name', 'base']
search_fields = ['name']
def get_permissions(self):
if self.request.method.lower() in ['get', 'options']:
self.permission_classes = (IsOrgAdmin,)
return super().get_permissions()
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
self.permission_denied(
request, message={"detail": "Internal platform"}
)
return super().check_object_permissions(request, obj)
class AssetTaskCreateApi(generics.CreateAPIView):
model = Asset
serializer_class = serializers.AssetTaskSerializer
permission_classes = (IsOrgAdmin,)
def get_object(self):
pk = self.kwargs.get("pk")
instance = get_object_or_404(Asset, pk=pk)
return instance
def perform_create(self, serializer):
asset = self.get_object()
action = serializer.validated_data["action"]
if action == "refresh":
task = update_asset_hardware_info_manual.delay(asset)
else:
task = test_asset_connectivity_manual.delay(asset)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
class AssetGatewayListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewayWithAuthSerializer
model = Asset
def get_queryset(self):
asset_id = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
if not asset.domain:
return []
queryset = asset.domain.gateways.filter(protocol='ssh')
return queryset

View File

@@ -1,157 +0,0 @@
# -*- coding: utf-8 -*-
#
import coreapi
from django.conf import settings
from rest_framework.response import Response
from rest_framework import generics, filters
from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger
from common.mixins import CommonApiMixin
from ..backends import AssetUserManager
from ..models import Asset, Node, SystemUser
from .. import serializers
from ..tasks import (
test_asset_users_connectivity_manual, push_system_user_a_asset_manual
)
__all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoViewSet', 'AssetUserTaskCreateAPI',
]
logger = get_logger(__name__)
class AssetUserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
kwargs = {}
for field in view.filter_fields:
value = request.GET.get(field)
if not value:
continue
if field == "node_id":
value = get_object_or_none(Node, pk=value)
kwargs["node"] = value
continue
elif field == "asset_id":
field = "asset"
kwargs[field] = value
if kwargs:
queryset = queryset.filter(**kwargs)
logger.debug("Filter {}".format(kwargs))
return queryset
class AssetUserSearchBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
value = request.GET.get('search')
if not value:
return queryset
queryset = queryset.search(value)
return queryset
class AssetUserLatestFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='latest', location='query', required=False,
type='string', example='1',
description='Only the latest version'
)
]
def filter_queryset(self, request, queryset, view):
latest = request.GET.get('latest') == '1'
if latest:
queryset = queryset.distinct()
return queryset
class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
serializer_classes = {
'default': serializers.AssetUserWriteSerializer,
'display': serializers.AssetUserReadSerializer,
'retrieve': serializers.AssetUserReadSerializer,
}
permission_classes = [IsOrgAdminOrAppUser]
filter_fields = [
"id", "ip", "hostname", "username",
"asset_id", "node_id",
"prefer", "prefer_id",
]
search_fields = ["ip", "hostname", "username"]
filter_backends = [
AssetUserFilterBackend, AssetUserSearchBackend,
AssetUserLatestFilterBackend,
]
def allow_bulk_destroy(self, qs, filtered):
return False
def get_object(self):
pk = self.kwargs.get("pk")
if pk is None:
return
queryset = self.get_queryset()
obj = queryset.get(id=pk)
return obj
def get_exception_handler(self):
def handler(e, context):
logger.error(e, exc_info=True)
return Response({"error": str(e)}, status=400)
return handler
def perform_destroy(self, instance):
manager = AssetUserManager()
manager.delete(instance)
def get_queryset(self):
manager = AssetUserManager()
queryset = manager.all()
return queryset
class AssetUserAuthInfoViewSet(AssetUserViewSet):
serializer_classes = {"default": serializers.AssetUserAuthInfoSerializer}
http_method_names = ['get', 'post']
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
class AssetUserTaskCreateAPI(generics.CreateAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetUserTaskSerializer
filter_backends = AssetUserViewSet.filter_backends
filter_fields = AssetUserViewSet.filter_fields
def get_asset_users(self):
manager = AssetUserManager()
queryset = manager.all()
for cls in self.filter_backends:
queryset = cls().filter_queryset(self.request, queryset, self)
return list(queryset)
def perform_create(self, serializer):
asset_users = self.get_asset_users()
# action = serializer.validated_data["action"]
# only this
# if action == "test":
task = test_asset_users_connectivity_manual.delay(asset_users)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
return task
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return handler

View File

@@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.shortcuts import get_object_or_404
from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import CommandFilter, CommandFilterRule
from .. import serializers
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(OrgBulkModelViewSet):
model = CommandFilter
filter_fields = ("name",)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterSerializer
class CommandFilterRuleViewSet(OrgBulkModelViewSet):
model = CommandFilterRule
filter_fields = ("content",)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer
def get_queryset(self):
fpk = self.kwargs.get('filter_pk')
if not fpk:
return CommandFilterRule.objects.none()
cmd_filter = get_object_or_404(CommandFilter, pk=fpk)
return cmd_filter.rules.all()

View File

@@ -1,49 +0,0 @@
# ~*~ coding: utf-8 ~*~
from rest_framework.views import APIView, Response
from django.views.generic.detail import SingleObjectMixin
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet
from ..models import Domain, Gateway
from .. import serializers
logger = get_logger(__file__)
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(OrgBulkModelViewSet):
model = Domain
filter_fields = ("name", )
search_fields = filter_fields
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DomainSerializer
def get_serializer_class(self):
if self.request.query_params.get('gateway'):
return serializers.DomainWithGatewaySerializer
return super().get_serializer_class()
class GatewayViewSet(OrgBulkModelViewSet):
model = Gateway
filter_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = ("domain__name", "name", "username", "ip")
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsOrgAdmin,)
object = None
def post(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all())
local_port = self.request.data.get('port') or self.object.port
ok, e = self.object.test_connective(local_port=local_port)
if ok:
return Response("ok")
else:
return Response({"error": e}, status=400)

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
#
from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsValidUser
from orgs.utils import tmp_to_root_org
from ..models import FavoriteAsset
from ..serializers import FavoriteAssetSerializer
__all__ = ['FavoriteAssetViewSet']
class FavoriteAssetViewSet(BulkModelViewSet):
serializer_class = FavoriteAssetSerializer
permission_classes = (IsValidUser,)
filter_fields = ['asset']
def dispatch(self, request, *args, **kwargs):
with tmp_to_root_org():
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
queryset = FavoriteAsset.objects.filter(user=self.request.user)
return queryset
def allow_bulk_destroy(self, qs, filtered):
return filtered.count() == 1

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
#
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from common.permissions import IsOrgAdmin
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
__all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
model = GatheredUser
serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id']
search_fields = ['username', 'asset__ip', 'asset__hostname']

View File

@@ -1,44 +0,0 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.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.
from django.db.models import Count
from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
logger = get_logger(__file__)
__all__ = ['LabelViewSet']
class LabelViewSet(OrgBulkModelViewSet):
model = Label
filter_fields = ("name", "value")
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer
def list(self, request, *args, **kwargs):
if request.query_params.get("distinct"):
self.serializer_class = serializers.LabelDistinctSerializer
self.queryset = self.queryset.values("name").distinct()
return super().list(request, *args, **kwargs)
def get_queryset(self):
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
return self.queryset

View File

@@ -1,289 +0,0 @@
# ~*~ coding: utf-8 ~*~
from collections import namedtuple
from rest_framework import status
from rest_framework.serializers import ValidationError
from rest_framework.response import Response
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404, Http404
from common.utils import get_logger, get_object_or_none
from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgModelViewSet
from orgs.mixins import generics
from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import (
update_node_assets_hardware_info_manual,
test_node_assets_connectivity_manual,
)
from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'NodeListAsTreeApi',
'NodeChildrenAsTreeApi',
'NodeTaskCreateApi',
]
class NodeViewSet(OrgModelViewSet):
model = Node
filter_fields = ('value', 'key', 'id')
search_fields = ('value', )
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
# 仅支持根节点指直接创建子节点下的节点需要通过children接口创建
def perform_create(self, serializer):
child_key = Node.org_root().get_next_child_key()
serializer.validated_data["key"] = child_key
serializer.save()
def perform_update(self, serializer):
node = self.get_object()
if node.is_org_root() and node.value != serializer.validated_data['value']:
msg = _("You can't update the root node name")
raise ValidationError({"error": msg})
return super().perform_update(serializer)
def destroy(self, request, *args, **kwargs):
node = self.get_object()
if node.has_children_or_has_assets():
error = _("Deletion failed and the node contains children or assets")
return Response(data={'error': error}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
class NodeListAsTreeApi(generics.ListAPIView):
"""
获取节点列表树
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
@staticmethod
def to_tree_queryset(queryset):
queryset = [node.as_tree_node() for node in queryset]
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.to_tree_queryset(queryset)
return queryset
class NodeChildrenApi(generics.ListCreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
is_initial = False
def initial(self, request, *args, **kwargs):
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
serializer.instance = node
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key")
if not pk and not key:
node = Node.org_root()
self.is_initial = True
return node
if pk:
node = get_object_or_404(Node, pk=pk)
else:
node = get_object_or_404(Node, key=key)
return node
def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all"
if not self.instance:
return Node.objects.none()
if self.is_initial:
with_self = True
else:
with_self = False
if query_all:
queryset = self.instance.get_all_children(with_self=with_self)
else:
queryset = self.instance.get_children(with_self=with_self)
return queryset
class NodeChildrenAsTreeApi(NodeChildrenApi):
"""
节点子节点作为树返回,
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
model = Node
serializer_class = TreeNodeSerializer
http_method_names = ['get']
def get_queryset(self):
queryset = super().get_queryset()
queryset = [node.as_tree_node() for node in queryset]
queryset = self.add_assets_if_need(queryset)
queryset = sorted(queryset)
return queryset
def add_assets_if_need(self, queryset):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets:
return queryset
assets = self.instance.get_assets().only(
"id", "hostname", "ip", "os",
"org_id", "protocols",
)
for asset in assets:
queryset.append(asset.as_tree_node(self.instance))
return queryset
def check_need_refresh_nodes(self):
if self.request.query_params.get('refresh', '0') == '1':
Node.refresh_nodes()
class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer
def get_queryset(self):
node_id = self.kwargs.get('pk')
query_all = self.request.query_params.get('all')
instance = get_object_or_404(Node, pk=node_id)
if query_all:
return instance.get_all_assets()
else:
return instance.get_assets()
class NodeAddChildrenApi(generics.UpdateAPIView):
model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
def put(self, request, *args, **kwargs):
instance = self.get_object()
nodes_id = request.data.get("nodes")
children = [get_object_or_none(Node, id=pk) for pk in nodes_id]
for node in children:
if not node:
continue
node.parent = instance
return Response("OK")
class NodeAddAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
instance.assets.add(*tuple(assets))
class NodeRemoveAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
if instance != Node.org_root():
instance.assets.remove(*tuple(assets))
else:
assets = [asset for asset in assets if asset.nodes.count() > 1]
instance.assets.remove(*tuple(assets))
class NodeReplaceAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
for asset in assets:
asset.nodes.set([instance])
class NodeTaskCreateApi(generics.CreateAPIView):
model = Node
serializer_class = serializers.NodeTaskSerializer
permission_classes = (IsOrgAdmin,)
def get_object(self):
node_id = self.kwargs.get('pk')
node = get_object_or_none(self.model, id=node_id)
return node
@staticmethod
def set_serializer_data(s, task):
data = getattr(s, '_data', {})
data["task"] = task.id
setattr(s, '_data', data)
@staticmethod
def refresh_nodes_cache():
Node.refresh_nodes()
Task = namedtuple('Task', ['id'])
task = Task(id="0")
return task
def perform_create(self, serializer):
action = serializer.validated_data["action"]
node = self.get_object()
if action == "refresh_cache" and node is None:
task = self.refresh_nodes_cache()
self.set_serializer_data(serializer, task)
return
if node is None:
raise Http404()
if action == "refresh":
task = update_node_assets_hardware_info_manual.delay(node)
else:
task = test_node_assets_connectivity_manual.delay(node)
self.set_serializer_data(serializer, task)

View File

@@ -1,127 +0,0 @@
# ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsAppUser
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from orgs.utils import tmp_to_org
from ..models import SystemUser, Asset
from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer
from ..tasks import (
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
push_system_user_a_asset_manual,
)
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi',
]
class SystemUserViewSet(OrgBulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
model = SystemUser
filter_fields = ("name", "username", "protocol")
search_fields = filter_fields
serializer_class = serializers.SystemUserSerializer
serializer_classes = {
'default': serializers.SystemUserSerializer,
'list': serializers.SystemUserListSerializer,
}
permission_classes = (IsOrgAdminOrAppUser,)
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
Get system user auth info
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.clear_auth()
return Response(status=204)
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return handler
def get_object(self):
instance = super().get_object()
username = instance.username
if instance.username_same_with_user:
username = self.request.query_params.get("username")
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, pk=asset_id)
with tmp_to_org(asset.org_id):
instance.load_asset_special_auth(asset=asset, username=username)
return instance
class SystemUserTaskApi(generics.CreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.SystemUserTaskSerializer
def do_push(self, system_user, asset=None):
if asset is None:
task = push_system_user_to_assets_manual.delay(system_user)
else:
username = self.request.query_params.get('username')
task = push_system_user_a_asset_manual.delay(
system_user, asset, username=username
)
return task
@staticmethod
def do_test(system_user, asset=None):
task = test_system_user_connectivity_manual.delay(system_user)
return task
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk)
def perform_create(self, serializer):
action = serializer.validated_data["action"]
asset = serializer.validated_data.get('asset')
system_user = self.get_object()
if action == 'push':
task = self.do_push(system_user, asset)
else:
task = self.do_test(system_user, asset)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get_serializer_class(self):
from ..serializers import CommandFilterRuleSerializer
return CommandFilterRuleSerializer
def get_queryset(self):
pk = self.kwargs.get('pk', None)
system_user = get_object_or_404(SystemUser, pk=pk)
return system_user.cmd_filter_rules

View File

@@ -1,136 +0,0 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models import F, Value
from django.db.models.signals import m2m_changed
from django.db.models.functions import Concat
from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from .. import models, serializers
__all__ = [
'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet',
'SystemUserUserRelationViewSet',
]
class RelationMixin:
def get_queryset(self):
queryset = self.model.objects.all()
org_id = current_org.org_id()
if org_id is not None:
queryset = queryset.filter(systemuser__org_id=org_id)
queryset = queryset.annotate(systemuser_display=Concat(
F('systemuser__name'), Value('('), F('systemuser__username'),
Value(')')
))
return queryset
def send_post_add_signal(self, instance):
if not isinstance(instance, list):
instance = [instance]
system_users_objects_map = defaultdict(list)
model, object_field = self.get_objects_attr()
for i in instance:
_id = getattr(i, object_field).id
system_users_objects_map[i.systemuser].append(_id)
sender = self.get_sender()
for system_user, objects in system_users_objects_map.items():
m2m_changed.send(
sender=sender, instance=system_user, action='post_add',
reverse=False, model=model, pk_set=objects
)
def get_sender(self):
return self.model
def get_objects_attr(self):
return models.Asset, 'asset'
def perform_create(self, serializer):
instance = serializer.save()
self.send_post_add_signal(instance)
class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
pass
class SystemUserAssetRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserAssetRelationSerializer
model = models.SystemUser.assets.through
permission_classes = (IsOrgAdmin,)
filter_fields = [
'id', 'asset', 'systemuser',
]
search_fields = [
"id", "asset__hostname", "asset__ip",
"systemuser__name", "systemuser__username"
]
def get_objects_attr(self):
return models.Asset, 'asset'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
asset_display=Concat(
F('asset__hostname'), Value('('),
F('asset__ip'), Value(')')
)
)
return queryset
class SystemUserNodeRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserNodeRelationSerializer
model = models.SystemUser.nodes.through
permission_classes = (IsOrgAdmin,)
filter_fields = [
'id', 'node', 'systemuser',
]
search_fields = [
"node__value", "systemuser__name", "systemuser_username"
]
def get_objects_attr(self):
return models.Node, 'node'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(node_key=F('node__key'))
return queryset
class SystemUserUserRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserUserRelationSerializer
model = models.SystemUser.users.through
permission_classes = (IsOrgAdmin,)
filter_fields = [
'id', 'user', 'systemuser',
]
search_fields = [
"user__username", "user__name",
"systemuser__name", "systemuser__username",
]
def get_objects_attr(self):
from users.models import User
return User, 'user'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
user_display=Concat(
F('user__name'), Value('('),
F('user__username'), Value(')')
)
)
return queryset

View File

@@ -1,25 +0,0 @@
from __future__ import unicode_literals
from django.apps import AppConfig
from django.db.models.signals import post_migrate
def initial_some_nodes():
from .models import Node
Node.initial_some_nodes()
def initial_some_nodes_callback(sender, **kwargs):
initial_some_nodes()
class AssetsConfig(AppConfig):
name = 'assets'
def ready(self):
super().ready()
from . import signals_handler
try:
initial_some_nodes()
except Exception:
post_migrate.connect(initial_some_nodes_callback, sender=self)

View File

@@ -1 +0,0 @@
from .manager import AssetUserManager

View File

@@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
#
from abc import abstractmethod
from ..models import Asset
class BaseBackend:
@abstractmethod
def all(self):
pass
@abstractmethod
def filter(self, username=None, hostname=None, ip=None, assets=None,
node=None, prefer_id=None, **kwargs):
pass
@abstractmethod
def search(self, item):
pass
@abstractmethod
def get_queryset(self):
pass
@abstractmethod
def delete(self, union_id):
pass
@staticmethod
def qs_to_values(qs):
values = qs.values(
'hostname', 'ip', "asset_id",
'username', 'password', 'private_key', 'public_key',
'score', 'version',
"asset_username", "union_id",
'date_created', 'date_updated',
'org_id', 'backend',
)
return values
@staticmethod
def make_assets_as_id(assets):
if not assets:
return []
if isinstance(assets[0], Asset):
assets = [a.id for a in assets]
return assets

View File

@@ -1,318 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from functools import reduce
from django.db.models import F, CharField, Value, IntegerField, Q, Count
from django.db.models.functions import Concat
from common.utils import get_object_or_none
from orgs.utils import current_org
from ..models import AuthBook, SystemUser, Asset, AdminUser
from .base import BaseBackend
class DBBackend(BaseBackend):
union_id_length = 2
def __init__(self, queryset=None):
if queryset is None:
queryset = self.all()
self.queryset = queryset
def _clone(self):
return self.__class__(self.queryset)
def all(self):
return AuthBook.objects.none()
def count(self):
return self.queryset.count()
def get_queryset(self):
return self.queryset
def delete(self, union_id):
cleaned_union_id = union_id.split('_')
# 如果union_id通不过本检查代表可能不是本backend, 应该返回空
if not self._check_union_id(union_id, cleaned_union_id):
return
return self._perform_delete_by_union_id(cleaned_union_id)
def _perform_delete_by_union_id(self, union_id_cleaned):
pass
def filter(self, assets=None, node=None, prefer=None, prefer_id=None,
union_id=None, id__in=None, **kwargs):
clone = self._clone()
clone._filter_union_id(union_id)
clone._filter_prefer(prefer, prefer_id)
clone._filter_node(node)
clone._filter_assets(assets)
clone._filter_other(kwargs)
clone._filter_id_in(id__in)
return clone
def _filter_union_id(self, union_id):
if not union_id:
return
cleaned_union_id = union_id.split('_')
# 如果union_id通不过本检查代表可能不是本backend, 应该返回空
if not self._check_union_id(union_id, cleaned_union_id):
self.queryset = self.queryset.none()
return
return self._perform_filter_union_id(union_id, cleaned_union_id)
def _check_union_id(self, union_id, cleaned_union_id):
return union_id and len(cleaned_union_id) == self.union_id_length
def _perform_filter_union_id(self, union_id, union_id_cleaned):
self.queryset = self.queryset.filter(union_id=union_id)
def _filter_assets(self, assets):
assets_id = self.make_assets_as_id(assets)
if assets_id:
self.queryset = self.queryset.filter(asset_id__in=assets_id)
def _filter_node(self, node):
pass
def _filter_id_in(self, ids):
if ids and isinstance(ids, list):
self.queryset = self.queryset.filter(union_id__in=ids)
@staticmethod
def clean_kwargs(kwargs):
return {k: v for k, v in kwargs.items() if v}
def _filter_other(self, kwargs):
kwargs = self.clean_kwargs(kwargs)
if kwargs:
self.queryset = self.queryset.filter(**kwargs)
def _filter_prefer(self, prefer, prefer_id):
pass
def search(self, item):
qs = []
for i in ['hostname', 'ip', 'username']:
kwargs = {i + '__startswith': item}
qs.append(Q(**kwargs))
q = reduce(lambda x, y: x | y, qs)
clone = self._clone()
clone.queryset = clone.queryset.filter(q).distinct()
return clone
class SystemUserBackend(DBBackend):
model = SystemUser.assets.through
backend = 'system_user'
prefer = backend
base_score = 0
union_id_length = 2
def _filter_prefer(self, prefer, prefer_id):
if prefer and prefer != self.prefer:
self.queryset = self.queryset.none()
if prefer_id:
self.queryset = self.queryset.filter(systemuser__id=prefer_id)
def _perform_filter_union_id(self, union_id, union_id_cleaned):
system_user_id, asset_id = union_id_cleaned
self.queryset = self.queryset.filter(
asset_id=asset_id, systemuser__id=system_user_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
system_user_id, asset_id = union_id_cleaned
system_user = get_object_or_none(SystemUser, pk=system_user_id)
asset = get_object_or_none(Asset, pk=asset_id)
if all((system_user, asset)):
system_user.assets.remove(asset)
def _filter_node(self, node):
if node:
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
def get_annotate(self):
kwargs = dict(
hostname=F("asset__hostname"),
ip=F("asset__ip"),
username=F("systemuser__username"),
password=F("systemuser__password"),
private_key=F("systemuser__private_key"),
public_key=F("systemuser__public_key"),
score=F("systemuser__priority") + self.base_score,
version=Value(0, IntegerField()),
date_created=F("systemuser__date_created"),
date_updated=F("systemuser__date_updated"),
asset_username=Concat(F("asset__id"), Value("_"),
F("systemuser__username"),
output_field=CharField()),
union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"),
output_field=CharField()),
org_id=F("asset__org_id"),
backend=Value(self.backend, CharField())
)
return kwargs
def get_filter(self):
return dict(
systemuser__username_same_with_user=False,
)
def all(self):
kwargs = self.get_annotate()
filters = self.get_filter()
qs = self.model.objects.all().annotate(**kwargs)
if current_org.org_id() is not None:
filters['org_id'] = current_org.org_id()
qs = qs.filter(**filters)
qs = self.qs_to_values(qs)
return qs
class DynamicSystemUserBackend(SystemUserBackend):
backend = 'system_user_dynamic'
prefer = 'system_user'
union_id_length = 3
def get_annotate(self):
kwargs = super().get_annotate()
kwargs.update(dict(
username=F("systemuser__users__username"),
asset_username=Concat(
F("asset__id"), Value("_"),
F("systemuser__users__username"),
output_field=CharField()
),
union_id=Concat(
F("systemuser_id"), Value("_"), F("asset_id"),
Value("_"), F("systemuser__users__id"),
output_field=CharField()
),
users_count=Count('systemuser__users'),
))
return kwargs
def _perform_filter_union_id(self, union_id, union_id_cleaned):
system_user_id, asset_id, user_id = union_id_cleaned
self.queryset = self.queryset.filter(
asset_id=asset_id, systemuser_id=system_user_id,
union_id=union_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
system_user_id, asset_id, user_id = union_id_cleaned
system_user = get_object_or_none(SystemUser, pk=system_user_id)
if not system_user:
return
system_user.users.remove(user_id)
if system_user.users.count() == 0:
system_user.assets.remove(asset_id)
def get_filter(self):
return dict(
users_count__gt=0,
systemuser__username_same_with_user=True
)
class AdminUserBackend(DBBackend):
model = Asset
backend = 'admin_user'
prefer = backend
base_score = 200
def _filter_prefer(self, prefer, prefer_id):
if prefer and prefer != self.backend:
self.queryset = self.queryset.none()
if prefer_id:
self.queryset = self.queryset.filter(admin_user__id=prefer_id)
def _filter_node(self, node):
if node:
self.queryset = self.queryset.filter(nodes__id=node.id)
def _perform_filter_union_id(self, union_id, union_id_cleaned):
admin_user_id, asset_id = union_id_cleaned
self.queryset = self.queryset.filter(
id=asset_id, admin_user_id=admin_user_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
raise PermissionError(_("Could not remove asset admin user"))
def all(self):
qs = self.model.objects.all().annotate(
asset_id=F("id"),
username=F("admin_user__username"),
password=F("admin_user__password"),
private_key=F("admin_user__private_key"),
public_key=F("admin_user__public_key"),
score=Value(self.base_score, IntegerField()),
version=Value(0, IntegerField()),
date_updated=F("admin_user__date_updated"),
asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()),
union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()),
backend=Value(self.backend, CharField()),
)
qs = self.qs_to_values(qs)
return qs
class AuthbookBackend(DBBackend):
model = AuthBook
backend = 'db'
prefer = backend
base_score = 400
def _filter_node(self, node):
if node:
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
def _filter_prefer(self, prefer, prefer_id):
if not prefer or not prefer_id:
return
if prefer.lower() == "admin_user":
model = AdminUser
elif prefer.lower() == "system_user":
model = SystemUser
else:
self.queryset = self.queryset.none()
return
obj = get_object_or_none(model, pk=prefer_id)
if obj is None:
self.queryset = self.queryset.none()
return
username = obj.get_username()
if isinstance(username, str):
self.queryset = self.queryset.filter(username=username)
# dynamic system user return more username
else:
self.queryset = self.queryset.filter(username__in=username)
def _perform_filter_union_id(self, union_id, union_id_cleaned):
authbook_id, asset_id = union_id_cleaned
self.queryset = self.queryset.filter(
id=authbook_id, asset_id=asset_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
authbook_id, asset_id = union_id_cleaned
authbook = get_object_or_none(AuthBook, pk=authbook_id)
if authbook.is_latest:
raise PermissionError(_("Latest version could not be delete"))
AuthBook.objects.filter(id=authbook_id).delete()
def all(self):
qs = self.model.objects.all().annotate(
hostname=F("asset__hostname"),
ip=F("asset__ip"),
score=F('version') + self.base_score,
asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()),
union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()),
backend=Value(self.backend, CharField()),
)
qs = self.qs_to_values(qs)
return qs

View File

@@ -1,162 +0,0 @@
# -*- coding: utf-8 -*-
#
from itertools import chain, groupby
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from orgs.utils import current_org
from common.utils import get_logger, lazyproperty
from common.struct import QuerySetChain
from ..models import AssetUser, AuthBook
from .db import (
AuthbookBackend, SystemUserBackend, AdminUserBackend,
DynamicSystemUserBackend
)
logger = get_logger(__name__)
class NotSupportError(Exception):
pass
class AssetUserQueryset:
ObjectDoesNotExist = ObjectDoesNotExist
MultipleObjectsReturned = MultipleObjectsReturned
def __init__(self, backends=()):
self.backends = backends
self._distinct_queryset = None
def backends_queryset(self):
return [b.get_queryset() for b in self.backends]
@lazyproperty
def backends_counts(self):
return [b.count() for b in self.backends]
def filter(self, hostname=None, ip=None, username=None,
assets=None, asset=None, node=None,
id=None, prefer_id=None, prefer=None, id__in=None):
if not assets and asset:
assets = [asset]
kwargs = dict(
hostname=hostname, ip=ip, username=username,
assets=assets, node=node, prefer=prefer, prefer_id=prefer_id,
id__in=id__in, union_id=id,
)
logger.debug("Filter: {}".format(kwargs))
backends = []
for backend in self.backends:
clone = backend.filter(**kwargs)
backends.append(clone)
return self._clone(backends)
def _clone(self, backends=None):
if backends is None:
backends = self.backends
return self.__class__(backends)
def search(self, item):
backends = []
for backend in self.backends:
new = backend.search(item)
backends.append(new)
return self._clone(backends)
def distinct(self):
logger.debug("Distinct asset user queryset")
queryset_chain = chain(*(backend.get_queryset() for backend in self.backends))
queryset_sorted = sorted(
queryset_chain,
key=lambda item: (item["asset_username"], item["score"]),
reverse=True,
)
results = groupby(queryset_sorted, key=lambda item: item["asset_username"])
final = [next(result[1]) for result in results]
self._distinct_queryset = final
return self
def get(self, latest=False, **kwargs):
queryset = self.filter(**kwargs)
if latest:
queryset = queryset.distinct()
queryset = list(queryset)
count = len(queryset)
if count == 1:
data = queryset[0]
return data
elif count > 1:
msg = 'Should return 1 record, but get {}'.format(count)
raise MultipleObjectsReturned(msg)
else:
msg = 'No record found(org is {})'.format(current_org.name)
raise ObjectDoesNotExist(msg)
def get_latest(self, **kwargs):
return self.get(latest=True, **kwargs)
@staticmethod
def to_asset_user(data):
obj = AssetUser()
for k, v in data.items():
setattr(obj, k, v)
return obj
@property
def queryset(self):
if self._distinct_queryset is not None:
return self._distinct_queryset
return QuerySetChain(self.backends_queryset())
def count(self):
if self._distinct_queryset is not None:
return len(self._distinct_queryset)
else:
return sum(self.backends_counts)
def __getitem__(self, ndx):
return self.queryset.__getitem__(ndx)
def __iter__(self):
self._data = iter(self.queryset)
return self
def __next__(self):
return self.to_asset_user(next(self._data))
class AssetUserManager:
support_backends = (
('db', AuthbookBackend),
('system_user', SystemUserBackend),
('admin_user', AdminUserBackend),
('system_user_dynamic', DynamicSystemUserBackend),
)
def __init__(self):
self.backends = [backend() for name, backend in self.support_backends]
self._queryset = AssetUserQueryset(self.backends)
def all(self):
return self._queryset
def delete(self, obj):
name_backends_map = dict(self.support_backends)
backend_name = obj.backend
backend_cls = name_backends_map.get(backend_name)
union_id = obj.union_id
if backend_cls:
backend_cls().delete(union_id)
else:
raise ObjectDoesNotExist("Not backend found")
@staticmethod
def create(**kwargs):
# 使用create方法创建AuthBook对象解决并发创建问题添加锁机制
authbook = AuthBook.create(**kwargs)
return authbook
def __getattr__(self, item):
return getattr(self._queryset, item)

View File

@@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
#
# from django.conf import settings
# from .vault import VaultBackend

View File

@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
#

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
#

View File

@@ -1,119 +0,0 @@
# -*- coding: utf-8 -*-
#
import coreapi
from rest_framework import filters
from django.db.models import Q
from common.utils import dict_get_any, is_uuid, get_object_or_none
from .models import Node, Label
class AssetByNodeFilterBackend(filters.BaseFilterBackend):
fields = ['node', 'all']
def get_schema_fields(self, view):
return [
coreapi.Field(
name=field, location='query', required=False,
type='string', example='', description='', schema=None,
)
for field in self.fields
]
@staticmethod
def is_query_all(request):
query_all_arg = request.query_params.get('all')
show_current_asset_arg = request.query_params.get('show_current_asset')
query_all = query_all_arg == '1'
if show_current_asset_arg is not None:
query_all = show_current_asset_arg != '1'
return query_all
@staticmethod
def get_query_node(request):
node_id = dict_get_any(request.query_params, ['node', 'node_id'])
if not node_id:
return None, False
if is_uuid(node_id):
node = get_object_or_none(Node, id=node_id)
else:
node = get_object_or_none(Node, key=node_id)
return node, True
@staticmethod
def perform_query(pattern, queryset):
return queryset.filter(nodes__key__regex=pattern).distinct()
def filter_queryset(self, request, queryset, view):
node, has_query_arg = self.get_query_node(request)
if not has_query_arg:
return queryset
if node is None:
return queryset
query_all = self.is_query_all(request)
if query_all:
pattern = node.get_all_children_pattern(with_self=True)
else:
# pattern = node.get_children_key_pattern(with_self=True)
# 只显示当前节点下资产
pattern = r"^{}$".format(node.key)
return self.perform_query(pattern, queryset)
class LabelFilterBackend(filters.BaseFilterBackend):
sep = ':'
query_arg = 'label'
def get_schema_fields(self, view):
example = self.sep.join(['os', 'linux'])
return [
coreapi.Field(
name=self.query_arg, location='query', required=False,
type='string', example=example, description=''
)
]
def get_query_labels(self, request):
labels_query = request.query_params.getlist(self.query_arg)
if not labels_query:
return None
q = None
for kv in labels_query:
if '#' in kv:
self.sep = '#'
if self.sep not in kv:
continue
key, value = kv.strip().split(self.sep)[:2]
if not all([key, value]):
continue
if q:
q |= Q(name=key, value=value)
else:
q = Q(name=key, value=value)
if not q:
return []
labels = Label.objects.filter(q, is_active=True)\
.values_list('id', flat=True)
return labels
def filter_queryset(self, request, queryset, view):
labels = self.get_query_labels(request)
if labels is None:
return queryset
if len(labels) == 0:
return queryset.none()
for label in labels:
queryset = queryset.filter(labels=label)
return queryset
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
@staticmethod
def perform_query(pattern, queryset):
return queryset.filter(asset__nodes__key__regex=pattern).distinct()

View File

@@ -1,15 +0,0 @@
"""
jumpserver.__app__.hands.py
~~~~~~~~~~~~~~~~~
This app depends other apps api, function .. should be import or write mack here.
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2018 by JumpServer Team.
:license: GPL v2, see LICENSE for more details.
"""
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup

View File

@@ -1,168 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-12-21 16:06
from __future__ import unicode_literals
import assets.models.utils
from django.db import migrations, models
import django.db.models.deletion
import uuid
def add_default_group(apps, schema_editor):
group_model = apps.get_model("assets", "AssetGroup")
db_alias = schema_editor.connection.alias
group_model.objects.using(db_alias).create(
name="Default"
)
def add_default_cluster(apps, schema_editor):
cluster_model = apps.get_model("assets", "Cluster")
db_alias = schema_editor.connection.alias
cluster_model.objects.using(db_alias).create(
name="Default"
)
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AdminUser',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('username', models.CharField(max_length=16, verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=32, null=True, verbose_name='Created by')),
('become', models.BooleanField(default=True)),
('become_method', models.CharField(choices=[('sudo', 'sudo'), ('su', 'su')], default='sudo', max_length=4)),
('become_user', models.CharField(default='root', max_length=64)),
('_become_pass', models.CharField(default='', max_length=128)),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Asset',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
('hostname', models.CharField(max_length=128, unique=True, verbose_name='Hostname')),
('port', models.IntegerField(default=22, verbose_name='Port')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('type', models.CharField(blank=True, choices=[('Server', 'Server'), ('VM', 'VM'), ('Switch', 'Switch'), ('Router', 'Router'), ('Firewall', 'Firewall'), ('Storage', 'Storage')], default='Server', max_length=16, null=True, verbose_name='Asset type')),
('env', models.CharField(blank=True, choices=[('Prod', 'Production'), ('Dev', 'Development'), ('Test', 'Testing')], default='Prod', max_length=8, null=True, verbose_name='Asset environment')),
('status', models.CharField(blank=True, choices=[('In use', 'In use'), ('Out of use', 'Out of use')], default='In use', max_length=12, null=True, verbose_name='Asset status')),
('public_ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='Public IP')),
('remote_card_ip', models.CharField(blank=True, max_length=16, null=True, verbose_name='Remote control card IP')),
('cabinet_no', models.CharField(blank=True, max_length=32, null=True, verbose_name='Cabinet number')),
('cabinet_pos', models.IntegerField(blank=True, null=True, verbose_name='Cabinet position')),
('number', models.CharField(blank=True, max_length=32, null=True, verbose_name='Asset number')),
('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')),
('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')),
('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')),
('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')),
('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')),
('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')),
('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')),
('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')),
('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')),
('platform', models.CharField(blank=True, max_length=128, null=True, verbose_name='Platform')),
('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')),
('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')),
('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')),
('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')),
],
),
migrations.CreateModel(
name='AssetGroup',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64, unique=True, verbose_name='Name')),
('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Cluster',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=32, verbose_name='Name')),
('bandwidth', models.CharField(blank=True, max_length=32, verbose_name='Bandwidth')),
('contact', models.CharField(blank=True, max_length=128, verbose_name='Contact')),
('phone', models.CharField(blank=True, max_length=32, verbose_name='Phone')),
('address', models.CharField(blank=True, max_length=128, verbose_name='Address')),
('intranet', models.TextField(blank=True, verbose_name='Intranet')),
('extranet', models.TextField(blank=True, verbose_name='Extranet')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('operator', models.CharField(blank=True, max_length=32, verbose_name='Operator')),
('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='SystemUser',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('username', models.CharField(max_length=16, verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=32, null=True, verbose_name='Created by')),
('priority', models.IntegerField(default=10, verbose_name='Priority')),
('protocol', models.CharField(choices=[('ssh', 'ssh')], default='ssh', max_length=16, verbose_name='Protocol')),
('auto_push', models.BooleanField(default=True, verbose_name='Auto push')),
('sudo', models.TextField(default='/sbin/ifconfig', verbose_name='Sudo')),
('shell', models.CharField(default='/bin/bash', max_length=64, verbose_name='Shell')),
('cluster', models.ManyToManyField(blank=True, to='assets.Cluster', verbose_name='Cluster')),
],
options={
'ordering': ['name'],
},
),
migrations.AddField(
model_name='asset',
name='cluster',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
migrations.AddField(
model_name='asset',
name='groups',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.AssetGroup', verbose_name='Asset groups'),
),
migrations.AlterUniqueTogether(
name='asset',
unique_together=set([('ip', 'port')]),
),
migrations.RunPython(add_default_cluster),
migrations.RunPython(add_default_group),
]

View File

@@ -1,158 +0,0 @@
# Generated by Django 2.1.7 on 2019-02-28 10:16
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
replaces = [('assets', '0002_auto_20180105_1807'), ('assets', '0003_auto_20180109_2331'), ('assets', '0004_auto_20180125_1218'), ('assets', '0005_auto_20180126_1637'), ('assets', '0006_auto_20180130_1502'), ('assets', '0007_auto_20180225_1815'), ('assets', '0008_auto_20180306_1804'), ('assets', '0009_auto_20180307_1212')]
dependencies = [
('assets', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='adminuser',
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
),
migrations.AlterModelOptions(
name='asset',
options={'verbose_name': 'Asset'},
),
migrations.AlterModelOptions(
name='assetgroup',
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
),
migrations.AlterModelOptions(
name='cluster',
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
),
migrations.AlterModelOptions(
name='systemuser',
options={'ordering': ['name'], 'verbose_name': 'System user'},
),
migrations.RemoveField(
model_name='asset',
name='cluster',
),
migrations.AlterField(
model_name='assetgroup',
name='created_by',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
),
migrations.CreateModel(
name='Label',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('value', models.CharField(max_length=128, verbose_name='Value')),
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
],
options={
'db_table': 'assets_label',
},
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value')},
),
migrations.AddField(
model_name='asset',
name='labels',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
),
migrations.RemoveField(
model_name='asset',
name='cabinet_no',
),
migrations.RemoveField(
model_name='asset',
name='cabinet_pos',
),
migrations.RemoveField(
model_name='asset',
name='env',
),
migrations.RemoveField(
model_name='asset',
name='remote_card_ip',
),
migrations.RemoveField(
model_name='asset',
name='status',
),
migrations.RemoveField(
model_name='asset',
name='type',
),
migrations.CreateModel(
name='Node',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
('value', models.CharField(max_length=128, verbose_name='Value')),
('child_mark', models.IntegerField(default=0)),
('date_create', models.DateTimeField(auto_now_add=True)),
],
),
migrations.RemoveField(
model_name='asset',
name='groups',
),
migrations.RemoveField(
model_name='systemuser',
name='cluster',
),
migrations.AlterField(
model_name='asset',
name='admin_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AddField(
model_name='asset',
name='nodes',
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
),
migrations.AddField(
model_name='systemuser',
name='nodes',
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
),
migrations.AlterField(
model_name='adminuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='systemuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
]

View File

@@ -1,220 +0,0 @@
# Generated by Django 2.1.7 on 2019-02-28 10:16
import assets.models.utils
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
# assets.migrations.0017_auto_20180702_1415
def migrate_win_to_ssh_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
class Migration(migrations.Migration):
replaces = [('assets', '0010_auto_20180307_1749'), ('assets', '0011_auto_20180326_0957'), ('assets', '0012_auto_20180404_1302'), ('assets', '0013_auto_20180411_1135'), ('assets', '0014_auto_20180427_1245'), ('assets', '0015_auto_20180510_1235'), ('assets', '0016_auto_20180511_1203'), ('assets', '0017_auto_20180702_1415'), ('assets', '0018_auto_20180807_1116'), ('assets', '0019_auto_20180816_1320')]
dependencies = [
('assets', '0009_auto_20180307_1212'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
),
migrations.CreateModel(
name='Domain',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
],
),
migrations.CreateModel(
name='Gateway',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
('port', models.IntegerField(default=22, verbose_name='Port')),
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='asset',
name='domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
migrations.AddField(
model_name='systemuser',
name='assets',
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
),
migrations.AlterField(
model_name='systemuser',
name='sudo',
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
migrations.AddField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AddField(
model_name='systemuser',
name='login_mode',
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.RunPython(
code=migrate_win_to_ssh_protocol,
),
migrations.AddField(
model_name='adminuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='asset',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='domain',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='gateway',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='label',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='node',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='systemuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AlterField(
model_name='adminuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='asset',
name='hostname',
field=models.CharField(max_length=128, verbose_name='Hostname'),
),
migrations.AlterField(
model_name='gateway',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='systemuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterUniqueTogether(
name='adminuser',
unique_together={('name', 'org_id')},
),
migrations.AddField(
model_name='asset',
name='cpu_vcpus',
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
),
migrations.AlterUniqueTogether(
name='asset',
unique_together={('org_id', 'hostname')},
),
migrations.AlterUniqueTogether(
name='gateway',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='systemuser',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value', 'org_id')},
),
]

View File

@@ -1,48 +0,0 @@
# Generated by Django 2.0.7 on 2018-08-16 08:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0019_auto_20180816_1320'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='asset',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='domain',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='gateway',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='label',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='node',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='systemuser',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
]

View File

@@ -1,25 +0,0 @@
# Generated by Django 2.1 on 2018-09-03 03:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0020_auto_20180816_1652'),
]
operations = [
migrations.AlterModelOptions(
name='domain',
options={'verbose_name': 'Domain'},
),
migrations.AlterModelOptions(
name='gateway',
options={'verbose_name': 'Gateway'},
),
migrations.AlterModelOptions(
name='node',
options={'verbose_name': 'Node'},
),
]

View File

@@ -1,56 +0,0 @@
# Generated by Django 2.1.1 on 2018-10-12 09:17
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0021_auto_20180903_1132'),
]
operations = [
migrations.CreateModel(
name='CommandFilter',
fields=[
('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)),
('name', models.CharField(max_length=64, verbose_name='Name')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='CommandFilterRule',
fields=[
('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)),
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
('content', models.TextField(help_text='One line one command', max_length=1024, verbose_name='Content')),
('action', models.IntegerField(choices=[(0, 'Deny'), (1, 'Allow')], default=0, verbose_name='Action')),
('comment', models.CharField(blank=True, default='', max_length=64, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='assets.CommandFilter', verbose_name='Filter')),
],
options={
'ordering': ('priority', 'action'),
},
),
migrations.AddField(
model_name='systemuser',
name='cmd_filters',
field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.CommandFilter', verbose_name='Command filter'),
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 2.1.1 on 2018-10-16 08:50
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0022_auto_20181012_1717'),
]
operations = [
migrations.AlterModelOptions(
name='commandfilterrule',
options={'ordering': ('-priority', 'action')},
),
migrations.AlterField(
model_name='commandfilterrule',
name='priority',
field=models.IntegerField(default=50, help_text='1-100, the higher will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
),
migrations.AlterField(
model_name='systemuser',
name='priority',
field=models.IntegerField(default=20, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-19 08:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0023_auto_20181016_1650'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
),
]

View File

@@ -1,21 +0,0 @@
# Generated by Django 2.1.7 on 2019-02-21 11:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0024_auto_20181219_1614'),
]
operations = [
migrations.AlterModelOptions(
name='commandfilter',
options={'verbose_name': 'Command filter'},
),
migrations.AlterModelOptions(
name='commandfilterrule',
options={'ordering': ('-priority', 'action'), 'verbose_name': 'Command filter rule'},
),
]

View File

@@ -1,43 +0,0 @@
# Generated by Django 2.1.7 on 2019-03-25 12:35
import assets.models.utils
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0025_auto_20190221_1902'),
]
operations = [
migrations.CreateModel(
name='AuthBook',
fields=[
('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)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
('is_latest', models.BooleanField(default=False, verbose_name='Latest version')),
('version', models.IntegerField(default=1, verbose_name='Version')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
],
options={
'verbose_name': 'AuthBook',
},
),
migrations.AlterModelOptions(
name='node',
options={'ordering': ['key'], 'verbose_name': 'Node'},
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 2.1.7 on 2019-05-21 09:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0026_auto_20190325_2035'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='ip',
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
),
migrations.AlterField(
model_name='asset',
name='public_ip',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
),
]

View File

@@ -1,29 +0,0 @@
# Generated by Django 2.1.7 on 2019-05-22 02:58
import django.core.validators
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0027_auto_20190521_1703'),
]
operations = [
migrations.CreateModel(
name='Protocol',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')),
('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')),
],
),
migrations.AddField(
model_name='asset',
name='protocols',
field=models.ManyToManyField(to='assets.Protocol',
verbose_name='Protocol'),
),
]

View File

@@ -1,13 +0,0 @@
# Generated by Django 2.1.7 on 2019-05-22 03:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0028_protocol'),
]
operations = [
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 2.1.7 on 2019-06-19 03:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0029_auto_20190522_1114'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='admin_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.AdminUser', verbose_name='Admin user'),
),
]

View File

@@ -1,53 +0,0 @@
# Generated by Django 2.1.7 on 2019-06-21 05:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0030_auto_20190619_1135'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
),
migrations.AlterField(
model_name='adminuser',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AlterField(
model_name='authbook',
name='date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
),
migrations.AlterField(
model_name='authbook',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AlterField(
model_name='gateway',
name='date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
),
migrations.AlterField(
model_name='gateway',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AlterField(
model_name='systemuser',
name='date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
),
migrations.AlterField(
model_name='systemuser',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
]

View File

@@ -1,75 +0,0 @@
# Generated by Django 2.1.7 on 2019-06-24 13:08
import assets.models.utils
import common.fields.model
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0031_auto_20190621_1332'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='adminuser',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='adminuser',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
migrations.AlterField(
model_name='authbook',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='authbook',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='authbook',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
migrations.AlterField(
model_name='gateway',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='gateway',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='gateway',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
migrations.AlterField(
model_name='systemuser',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='systemuser',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='systemuser',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
]

View File

@@ -1,73 +0,0 @@
# Generated by Django 2.1.7 on 2019-06-24 13:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0032_auto_20190624_2108'),
]
operations = [
migrations.RenameField(
model_name='adminuser',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='adminuser',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='authbook',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='authbook',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='gateway',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='gateway',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='systemuser',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='systemuser',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='adminuser',
old_name='_password',
new_name='password',
),
migrations.RenameField(
model_name='authbook',
old_name='_password',
new_name='password',
),
migrations.RenameField(
model_name='gateway',
old_name='_password',
new_name='password',
),
migrations.RenameField(
model_name='systemuser',
old_name='_password',
new_name='password',
),
]

View File

@@ -1,39 +0,0 @@
# Generated by Django 2.1.7 on 2019-07-05 05:48
from django.db import migrations
from django.db.models import F
from django.db.models import CharField, Value as V
from django.db.models.functions import Concat
def migrate_assets_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
assets = asset_model.objects.using(db_alias).all().annotate(
protocols_new=Concat(
'protocol', V('/'), 'port',
output_field=CharField(),
),
)
assets.update(protocols=F('protocols_new'))
class Migration(migrations.Migration):
dependencies = [
('assets', '0033_auto_20190624_2108'),
]
operations = [
migrations.RemoveField(
model_name='asset',
name='protocols',
),
migrations.AddField(
model_name='asset',
name='protocols',
field=CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
),
migrations.RunPython(migrate_assets_protocol),
migrations.DeleteModel(name='Protocol'),
]

View File

@@ -1,34 +0,0 @@
# Generated by Django 2.1.7 on 2019-07-11 12:18
import common.fields.model
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0034_auto_20190705_1348'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='authbook',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='gateway',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='systemuser',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.1.7 on 2019-07-16 07:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0035_auto_20190711_2018'),
]
operations = [
migrations.AlterField(
model_name='commandfilter',
name='name',
field=models.CharField(max_length=64, unique=True, verbose_name='Name'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.1.7 on 2019-07-24 12:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0036_auto_20190716_1535'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='_become_pass',
field=models.CharField(blank=True, default='', max_length=128),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 2.1.7 on 2019-09-11 08:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0037_auto_20190724_2002'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.1.7 on 2019-09-17 12:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0038_auto_20190911_1634'),
]
operations = [
migrations.AddField(
model_name='authbook',
name='is_active',
field=models.BooleanField(default=True, verbose_name='Is active'),
),
]

View File

@@ -1,36 +0,0 @@
# Generated by Django 2.1.7 on 2019-09-17 12:56
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0039_authbook_is_active'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='authbook',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 2.1.7 on 2019-09-18 04:10
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0040_auto_20190917_2056'),
]
operations = [
migrations.CreateModel(
name='GatheredUser',
fields=[
('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)),
('username', models.CharField(blank=True, db_index=True, max_length=32, verbose_name='Username')),
('present', models.BooleanField(default=True, verbose_name='Present')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
],
options={'ordering': ['asset'], 'verbose_name': 'GatherUser'},
),
]

View File

@@ -1,31 +0,0 @@
# Generated by Django 2.2.5 on 2019-10-16 08:38
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', '0041_gathereduser'),
]
operations = [
migrations.CreateModel(
name='FavoriteAsset',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created 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')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'asset')},
},
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 2.2.5 on 2019-11-14 03:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0042_favoriteasset'),
]
operations = [
migrations.AddField(
model_name='gathereduser',
name='date_last_login',
field=models.DateTimeField(null=True, verbose_name='Date last login'),
),
migrations.AddField(
model_name='gathereduser',
name='ip_last_login',
field=models.CharField(default='', max_length=39, verbose_name='IP last login'),
),
]

View File

@@ -1,48 +0,0 @@
# Generated by Django 2.2.7 on 2019-12-06 07:26
import common.fields.model
from django.db import migrations, models
def create_internal_platform(apps, schema_editor):
model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
type_platforms = (
('Linux', 'Linux', None),
('Unix', 'Unix', None),
('MacOS', 'MacOS', None),
('BSD', 'BSD', None),
('Windows', 'Windows', None),
('Windows2016', 'Windows', {'security': 'tls'}),
('Other', 'Other', None),
)
for name, base, meta in type_platforms:
model.objects.using(db_alias).create(
name=name, base=base, internal=True, meta=meta
)
class Migration(migrations.Migration):
dependencies = [
('assets', '0043_auto_20191114_1111'),
]
operations = [
migrations.CreateModel(
name='Platform',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')),
('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')),
('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')),
('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')),
('internal', models.BooleanField(default=False, verbose_name='Internal')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
],
options={
'verbose_name': 'Platform'
}
),
migrations.RunPython(create_internal_platform)
]

View File

@@ -1,47 +0,0 @@
# Generated by Django 2.2.7 on 2019-12-06 08:07
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
def migrate_platform_to_asset_type(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
platform_model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
platforms = platform_model.objects.using(db_alias).all()
platforms_map = {p.name: p for p in platforms}
for name, p in platforms_map.items():
asset_model.objects.using(db_alias)\
.filter(_platform=name)\
.update(platform=p)
class Migration(migrations.Migration):
dependencies = [
('assets', '0044_platform'),
]
operations = [
migrations.RenameField(
model_name='asset',
old_name='platform',
new_name='_platform',
),
migrations.AddField(
model_name='asset',
name='platform',
field=models.ForeignKey(
default=assets.models.asset.Platform.default,
on_delete=django.db.models.deletion.PROTECT,
related_name='assets', to='assets.Platform',
verbose_name='Platform'),
),
migrations.RunPython(migrate_platform_to_asset_type),
migrations.RemoveField(
model_name='asset',
name='_platform',
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.1.11 on 2019-12-18 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0045_auto_20191206_1607'),
]
operations = [
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc'), ('mysql', 'mysql')], default='ssh', max_length=16, verbose_name='Protocol'),
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 2.2.7 on 2020-01-06 07:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0046_auto_20191218_1705'),
]
operations = [
migrations.CreateModel(
name='AssetUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('assets.authbook',),
),
]

View File

@@ -1,35 +0,0 @@
# Generated by Django 2.2.7 on 2019-12-30 07:12
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0047_assetuser'),
]
operations = [
migrations.RemoveField(
model_name='authbook',
name='is_active',
),
migrations.AddField(
model_name='systemuser',
name='username_same_with_user',
field=models.BooleanField(default=False, verbose_name='Username same with user'),
),
migrations.AddField(
model_name='systemuser',
name='users',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Users'),
),
migrations.AddField(
model_name='systemuser',
name='groups',
field=models.ManyToManyField(blank=True, to='users.UserGroup',
verbose_name='User groups'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.2.7 on 2020-01-19 07:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0048_auto_20191230_1512'),
]
operations = [
migrations.AddField(
model_name='systemuser',
name='sftp_root',
field=models.CharField(default='tmp', max_length=128, verbose_name='SFTP Root'),
),
]

View File

@@ -1,15 +0,0 @@
from .base import *
from .asset import *
from .label import Label
from .user import *
from .asset_user import *
from .cluster import *
from .group import *
from .domain import *
from .node import *
from .cmd_filter import *
from .authbook import *
from .utils import *
from .authbook import *
from .gathered_user import *
from .favorite_asset import *

View File

@@ -1,389 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import uuid
import logging
import random
from functools import reduce
from collections import OrderedDict
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.fields.model import JsonDictTextField
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from .base import ConnectivityMixin
from .utils import Connectivity
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
logger = logging.getLogger(__name__)
def default_cluster():
from .cluster import Cluster
name = "Default"
defaults = {"name": name}
cluster, created = Cluster.objects.get_or_create(
defaults=defaults, name=name
)
return cluster.id
def default_node():
try:
from .node import Node
root = Node.org_root()
return root
except:
return None
class AssetManager(OrgManager):
def get_queryset(self):
return super().get_queryset().annotate(
platform_base=models.F('platform__base')
)
class AssetQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def valid(self):
return self.active()
def has_protocol(self, name):
return self.filter(protocols__contains=name)
class ProtocolsMixin:
protocols = ''
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet'),
(PROTOCOL_VNC, 'vnc'),
)
@property
def protocols_as_list(self):
if not self.protocols:
return []
return self.protocols.split(' ')
@property
def protocols_as_dict(self):
d = OrderedDict()
protocols = self.protocols_as_list
for i in protocols:
if '/' not in i:
continue
name, port = i.split('/')[:2]
if not all([name, port]):
continue
d[name] = int(port)
return d
@property
def protocols_as_json(self):
return [
{"name": name, "port": port}
for name, port in self.protocols_as_dict.items()
]
def has_protocol(self, name):
return name in self.protocols_as_dict
@property
def ssh_port(self):
return self.protocols_as_dict.get("ssh", 22)
class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
CACHE_TIME = 3600 * 24 * 7
id = ""
_all_nodes_keys = None
def get_nodes(self):
from .node import Node
nodes = self.nodes.all()
if not nodes:
nodes = Node.objects.filter(id=Node.org_root().id)
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestors(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
class Platform(models.Model):
CHARSET_CHOICES = (
('utf8', 'UTF-8'),
('gbk', 'GBK'),
)
BASE_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
defaults={'name': 'Linux'}, name='Linux'
)
return linux.id
def is_windows(self):
return self.base.lower() in ('windows',)
def is_unixlike(self):
return self.base.lower() in ("linux", "unix", "macos", "bsd")
def __str__(self):
return self.name
class Meta:
verbose_name = _("Platform")
# ordering = ('name',)
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=ProtocolsMixin.PROTOCOL_SSH,
choices=ProtocolsMixin.PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), 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=_('Is active'))
# Auth
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
# Some information
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = AssetManager.from_queryset(AssetQuerySet)()
_connectivity = None
def __str__(self):
return '{0.hostname}({0.ip})'.format(self)
@property
def is_valid(self):
warning = ''
if not self.is_active:
warning += ' inactive'
if warning:
return False, warning
return True, warning
@lazyproperty
def platform_base(self):
return self.platform.base
@lazyproperty
def admin_user_display(self):
return self.admin_user.name
@lazyproperty
def admin_user_username(self):
"""求可连接性时直接用用户名去取避免再查一次admin user
serializer 中直接通过annotate方式返回了这个
"""
return self.admin_user.username
def is_windows(self):
return self.platform.is_windows()
def is_unixlike(self):
return self.platform.is_unixlike()
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform_base not in ("Other",)
@property
def cpu_info(self):
info = ""
if self.cpu_model:
info += self.cpu_model
if self.cpu_count and self.cpu_cores:
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
return info
@property
def hardware_info(self):
if self.cpu_count:
return '{} Core {} {}'.format(
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
self.memory, self.disk_total
)
else:
return ''
@property
def connectivity(self):
if self._connectivity:
return self._connectivity
if not self.admin_user_username:
return Connectivity.unknown()
connectivity = ConnectivityMixin.get_asset_username_connectivity(
self, self.admin_user_username
)
return connectivity
@connectivity.setter
def connectivity(self, value):
if not self.admin_user:
return
self.admin_user.set_asset_connectivity(self, value)
def get_auth_info(self):
if not self.admin_user:
return {}
self.admin_user.load_asset_special_auth(self)
info = {
'username': self.admin_user.username,
'password': self.admin_user.password,
'private_key': self.admin_user.private_key_file,
}
return info
def as_node(self):
from .node import Node
fake_node = Node()
fake_node.id = self.id
fake_node.key = self.id
fake_node.value = self.hostname
fake_node.asset = self
fake_node.is_node = False
return fake_node
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
if self.platform_base.lower() == 'windows':
icon_skin = 'windows'
elif self.platform_base.lower() == 'linux':
icon_skin = 'linux'
data = {
'id': str(self.id),
'name': self.hostname,
'title': self.ip,
'pId': parent_node.key,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'asset',
'asset': {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'protocols': self.protocols_as_list,
'platform': self.platform_base,
}
}
}
tree_node = TreeNode(**data)
return tree_node
class Meta:
unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset")
@classmethod
def generate_fake(cls, count=100):
from .user import AdminUser, SystemUser
from random import seed, choice
from django.db import IntegrityError
from .node import Node
from orgs.utils import get_current_org
from orgs.models import Organization
org = get_current_org()
if not org or not org.is_real():
Organization.default().change_to()
nodes = list(Node.objects.all())
seed()
for i in range(count):
ip = [str(i) for i in random.sample(range(255), 4)]
asset = cls(ip='.'.join(ip),
hostname='.'.join(ip),
admin_user=choice(AdminUser.objects.all()),
created_by='Fake')
try:
asset.save()
asset.protocols = 'ssh/22'
if nodes and len(nodes) > 3:
_nodes = random.sample(nodes, 3)
else:
_nodes = [Node.default_node()]
asset.nodes.set(_nodes)
logger.debug('Generate fake asset : %s' % asset.ip)
except IntegrityError:
print('Error continue')
continue

View File

@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
#
from .authbook import AuthBook
class AssetUser(AuthBook):
hostname = ""
ip = ""
backend = ""
union_id = ""
asset_username = ""
class Meta:
proxy = True

View File

@@ -1,94 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.db import models, transaction
from django.db.models import Max
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgManager
from .base import BaseUser
__all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet):
def delete(self):
if self.count() > 1:
raise PermissionError(_("Bulk delete deny"))
return super().delete()
class AuthBookManager(OrgManager):
pass
class AuthBook(BaseUser):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
version = models.IntegerField(default=1, verbose_name=_('Version'))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
backend = "db"
# 用于system user和admin_user的动态设置
_connectivity = None
CONN_CACHE_KEY = "ASSET_USER_CONN_{}"
class Meta:
verbose_name = _('AuthBook')
def get_related_assets(self):
return [self.asset]
def generate_id_with_asset(self, asset):
return self.id
@classmethod
def get_max_version(cls, username, asset):
version_max = cls.objects.filter(username=username, asset=asset) \
.aggregate(Max('version'))
version_max = version_max['version__max'] or 0
return version_max
@classmethod
def create(cls, **kwargs):
"""
使用并发锁机制创建AuthBook对象, (主要针对并发创建 username, asset 相同的对象时)
并更新其他对象的 is_latest=False (其他对象: 与当前对象的 username, asset 相同)
同时设置自己的 is_latest=True, version=max_version + 1
"""
username = kwargs['username']
asset = kwargs['asset']
key_lock = 'KEY_LOCK_CREATE_AUTH_BOOK_{}_{}'.format(username, asset.id)
with cache.lock(key_lock):
with transaction.atomic():
cls.objects.filter(
username=username, asset=asset, is_latest=True
).update(is_latest=False)
max_version = cls.get_max_version(username, asset)
kwargs.update({
'version': max_version + 1,
'is_latest': True
})
obj = cls.objects.create(**kwargs)
return obj
@property
def connectivity(self):
return self.get_asset_connectivity(self.asset)
@property
def keyword(self):
return '{}_#_{}'.format(self.username, str(self.asset.id))
@property
def hostname(self):
return self.asset.hostname
@property
def ip(self):
return self.asset.ip
def __str__(self):
return '{}@{}'.format(self.username, self.asset)

View File

@@ -1,279 +0,0 @@
# -*- coding: utf-8 -*-
#
import io
import os
import uuid
from hashlib import md5
import sshpubkeys
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
)
from common.validators import alphanumeric
from common import fields
from orgs.mixins.models import OrgModelMixin
from .utils import Connectivity
logger = get_logger(__file__)
class ConnectivityMixin:
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
ASSET_USER_CACHE_TIME = 3600 * 24
id = ''
username = ''
@property
def part_id(self):
i = '-'.join(str(self.id).split('-')[:3])
return i
def set_connectivity(self, summary):
unreachable = summary.get('dark', {}).keys()
reachable = summary.get('contacted', {}).keys()
assets = self.get_related_assets()
if not isinstance(assets, list):
assets = assets.only('id', 'hostname', 'admin_user__id')
for asset in assets:
if asset.hostname in unreachable:
self.set_asset_connectivity(asset, Connectivity.unreachable())
elif asset.hostname in reachable:
self.set_asset_connectivity(asset, Connectivity.reachable())
else:
self.set_asset_connectivity(asset, Connectivity.unknown())
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
cache.delete(cache_key)
@property
def connectivity(self):
assets = self.get_related_assets()
if not isinstance(assets, list):
assets = assets.only('id', 'hostname', 'admin_user__id')
data = {
'unreachable': [],
'reachable': [],
'unknown': [],
}
for asset in assets:
connectivity = self.get_asset_connectivity(asset)
if connectivity.is_reachable():
data["reachable"].append(asset.hostname)
elif connectivity.is_unreachable():
data["unreachable"].append(asset.hostname)
else:
data["unknown"].append(asset.hostname)
return data
@property
def connectivity_amount(self):
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
amount = cache.get(cache_key)
if not amount:
amount = {k: len(v) for k, v in self.connectivity.items()}
cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
return amount
@classmethod
def get_asset_username_connectivity(cls, asset, username):
key = cls.CONNECTIVITY_ASSET_CACHE_KEY.format(username, asset.id)
return Connectivity.get(key)
def get_asset_connectivity(self, asset):
key = self.get_asset_connectivity_key(asset)
return Connectivity.get(key)
def get_asset_connectivity_key(self, asset):
return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)
def set_asset_connectivity(self, asset, c):
key = self.get_asset_connectivity_key(asset)
Connectivity.set(key, c)
class AuthMixin:
private_key = ''
password = ''
public_key = ''
username = ''
_prefer = 'system_user'
@property
def private_key_obj(self):
if self.private_key:
key_obj = ssh_key_string_to_obj(self.private_key, password=self.password)
return key_obj
else:
return None
@property
def private_key_file(self):
if not self.private_key_obj:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path
def get_private_key(self):
if not self.private_key_obj:
return None
string_io = io.StringIO()
self.private_key_obj.write_private_key(string_io)
private_key = string_io.getvalue()
return private_key
@property
def public_key_obj(self):
if self.public_key:
try:
return sshpubkeys.SSHKey(self.public_key)
except TabError:
pass
return None
def set_auth(self, password=None, private_key=None, public_key=None):
update_fields = []
if password:
self.password = password
update_fields.append('password')
if private_key:
self.private_key = private_key
update_fields.append('private_key')
if public_key:
self.public_key = public_key
update_fields.append('public_key')
if update_fields:
self.save(update_fields=update_fields)
def has_special_auth(self, asset=None):
from .authbook import AuthBook
queryset = AuthBook.objects.filter(username=self.username)
if asset:
queryset = queryset.filter(asset=asset)
return queryset.exists()
def get_asset_user(self, asset, username=None):
from ..backends import AssetUserManager
if username is None:
username = self.username
try:
manager = AssetUserManager()
other = manager.get_latest(
username=username, asset=asset,
prefer_id=self.id, prefer=self._prefer,
)
return other
except Exception as e:
logger.error(e, exc_info=True)
return None
def load_asset_special_auth(self, asset=None, username=None):
if not asset:
return self
instance = self.get_asset_user(asset, username=username)
if instance:
self._merge_auth(instance)
def _merge_auth(self, other):
if other.password:
self.password = other.password
if other.public_key or other.private_key:
self.private_key = other.private_key
self.public_key = other.public_key
def clear_auth(self):
self.password = ''
self.private_key = ''
self.public_key = ''
self.save()
@staticmethod
def gen_password():
return str(uuid.uuid4())
@staticmethod
def gen_key(username):
private_key, public_key = ssh_key_gen(
username=username
)
return private_key, public_key
def auto_gen_auth(self, password=True, key=True):
_password = None
_private_key = None
_public_key = None
if password:
_password = self.gen_password()
if key:
_private_key, _public_key = self.gen_key(self.username)
self.set_auth(
password=_password, private_key=_private_key,
public_key=_public_key
)
class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
ASSET_USER_CACHE_TIME = 600
_prefer = "system_user"
def get_related_assets(self):
assets = self.assets.filter(org_id=self.org_id)
return assets
def get_username(self):
return self.username
@lazyproperty
def assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cached = cache.get(cache_key)
if not cached:
cached = self.get_related_assets().count()
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
return cached
def expire_assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cache.delete(cache_key)
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'password': self.password,
'public_key': self.public_key,
'private_key': self.private_key_file,
}
class Meta:
abstract = True

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ['Cluster']
logger = logging.getLogger(__name__)
class Cluster(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=32, verbose_name=_('Name'))
admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
bandwidth = models.CharField(max_length=32, blank=True, verbose_name=_('Bandwidth'))
contact = models.CharField(max_length=128, blank=True, verbose_name=_('Contact'))
phone = models.CharField(max_length=32, blank=True, verbose_name=_('Phone'))
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
@classmethod
def initial(cls):
return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default Cluster'))[0]
class Meta:
ordering = ['name']
verbose_name = _("Cluster")
@classmethod
def generate_fake(cls, count=5):
from random import seed, choice
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
cluster = cls(name=forgery_py.name.full_name(),
bandwidth='200M',
contact=forgery_py.name.full_name(),
phone=forgery_py.address.phone(),
address=forgery_py.address.city() + forgery_py.address.street_address(),
# operator=choice(['北京联通', '北京电信', 'BGP全网通']),
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
cluster.save()
logger.debug('Generate fake asset group: %s' % cluster.name)
except IntegrityError:
print('Error continue')
continue

View File

@@ -1,97 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
import re
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin
__all__ = [
'CommandFilter', 'CommandFilterRule'
]
class CommandFilter(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_("Name"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
def __str__(self):
return self.name
class Meta:
verbose_name = _("Command filter")
class CommandFilterRule(OrgModelMixin):
TYPE_REGEX = 'regex'
TYPE_COMMAND = 'command'
TYPE_CHOICES = (
(TYPE_REGEX, _('Regex')),
(TYPE_COMMAND, _('Command')),
)
ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3)
ACTION_CHOICES = (
(ACTION_DENY, _('Deny')),
(ACTION_ALLOW, _('Allow')),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the higher will be match first"),
validators=[MinValueValidator(1), MaxValueValidator(100)])
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
class Meta:
ordering = ('-priority', 'action')
verbose_name = _("Command filter rule")
@lazyproperty
def _pattern(self):
if self.type == 'command':
regex = []
content = self.content.replace('\r\n', '\n')
for cmd in content.split('\n'):
cmd = re.escape(cmd)
cmd = cmd.replace('\\ ', '\s+')
if cmd[-1].isalpha():
regex.append(r'\b{0}\b'.format(cmd))
else:
regex.append(r'\b{0}'.format(cmd))
s = r'{}'.format('|'.join(regex))
else:
s = r'{0}'.format(self.content)
try:
_pattern = re.compile(s)
except:
_pattern = ''
return _pattern
def match(self, data):
found = self._pattern.search(data)
if not found:
return self.ACTION_UNKNOWN, ''
if self.action == self.ACTION_ALLOW:
return self.ACTION_ALLOW, found.group()
else:
return self.ACTION_DENY, found.group()
def __str__(self):
return '{} % {}'.format(self.type, self.content)

View File

@@ -1,100 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
import random
import re
import paramiko
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from .base import BaseUser
__all__ = ['Domain', 'Gateway']
class Domain(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True,
verbose_name=_('Date created'))
class Meta:
verbose_name = _("Domain")
def __str__(self):
return self.name
def has_gateway(self):
return self.gateway_set.filter(is_active=True).exists()
@property
def gateways(self):
return self.gateway_set.filter(is_active=True)
def random_gateway(self):
return random.choice(self.gateways)
class Gateway(BaseUser):
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=PROTOCOL_SSH, verbose_name=_("Protocol"))
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
def __str__(self):
return self.name
class Meta:
unique_together = [('name', 'org_id')]
verbose_name = _("Gateway")
def test_connective(self, local_port=None):
if local_port is None:
local_port = self.port
if self.password and not re.match(r'\w+$', self.password):
return False, _("Password should not contain special characters")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient()
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
proxy.connect(self.ip, port=self.port,
username=self.username,
password=self.password,
pkey=self.private_key_obj)
except(paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
paramiko.SSHException,
paramiko.ssh_exception.NoValidConnectionsError) as e:
return False, str(e)
try:
sock = proxy.get_transport().open_channel(
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
)
client.connect("127.0.0.1", port=local_port,
username=self.username,
password=self.password,
key_filename=self.private_key_file,
sock=sock,
timeout=5)
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
paramiko.AuthenticationException, TimeoutError) as e:
return False, str(e)
finally:
client.close()
return True, None

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from common.mixins.models import CommonModelMixin
__all__ = ['FavoriteAsset']
class FavoriteAsset(CommonModelMixin):
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE)
class Meta:
unique_together = ('user', 'asset')
@classmethod
def get_user_favorite_assets_id(cls, user):
return cls.objects.filter(user=user).values_list('asset', flat=True)

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
__all__ = ['GatheredUser']
class GatheredUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login"))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
@property
def hostname(self):
return self.asset.hostname
@property
def ip(self):
return self.asset.ip
class Meta:
verbose_name = _('GatherUser')
ordering = ['asset']
def __str__(self):
return '{}: {}'.format(self.asset.hostname, self.username)

View File

@@ -1,53 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import uuid
from django.db import models
import logging
from django.utils.translation import ugettext_lazy as _
__all__ = ['AssetGroup']
logger = logging.getLogger(__name__)
class AssetGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
class Meta:
ordering = ['name']
verbose_name = _("Asset group")
@classmethod
def initial(cls):
asset_group = cls(name=_('Default'), comment=_('Default asset group'))
asset_group.save()
@classmethod
def generate_fake(cls, count=100):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
group = cls(name=forgery_py.name.full_name(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
group.save()
logger.debug('Generate fake asset group: %s' % group.name)
except IntegrityError:
print('Error continue')
continue

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
class Label(OrgModelMixin):
SYSTEM_CATEGORY = "S"
USER_CATEGORY = "U"
CATEGORY_CHOICES = (
("S", _("System")),
("U", _("User"))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name"))
value = models.CharField(max_length=128, verbose_name=_("Value"))
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
default=USER_CATEGORY, verbose_name=_("Category"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
date_created = models.DateTimeField(
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
)
@classmethod
def get_queryset_group_by_name(cls):
names = cls.objects.values_list('name', flat=True)
for name in names:
yield name, cls.objects.filter(name=name)
def __str__(self):
return "{}:{}".format(self.name, self.value)
class Meta:
db_table = "assets_label"
unique_together = [('name', 'value', 'org_id')]

View File

@@ -1,586 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
import re
import time
from django.db import models, transaction
from django.db.models import Q
from django.db.utils import IntegrityError
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.cache import cache
from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from orgs.utils import get_current_org, tmp_to_org, current_org
from orgs.models import Organization
__all__ = ['Node']
logger = get_logger(__name__)
class NodeQuerySet(models.QuerySet):
def delete(self):
raise PermissionError("Bulk delete node deny")
class TreeCache:
updated_time_cache_key = 'NODE_TREE_UPDATED_AT_{}'
cache_time = 3600
assets_updated_time_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT_{}'
def __init__(self, tree, org_id):
now = time.time()
self.created_time = now
self.assets_created_time = now
self.tree = tree
self.org_id = org_id
def _has_changed(self, tp="tree"):
if tp == "assets":
key = self.assets_updated_time_cache_key.format(self.org_id)
else:
key = self.updated_time_cache_key.format(self.org_id)
updated_time = cache.get(key, 0)
if updated_time > self.created_time:
return True
else:
return False
@classmethod
def set_changed(cls, tp="tree", t=None, org_id=None):
if org_id is None:
org_id = current_org.id
if tp == "assets":
key = cls.assets_updated_time_cache_key.format(org_id)
else:
key = cls.updated_time_cache_key.format(org_id)
ttl = cls.cache_time
if not t:
t = time.time()
cache.set(key, t, ttl)
def tree_has_changed(self):
return self._has_changed("tree")
def set_tree_changed(self, t=None):
logger.debug("Set tree tree changed")
self.__class__.set_changed(t=t, tp="tree")
def assets_has_changed(self):
return self._has_changed("assets")
def set_tree_assets_changed(self, t=None):
logger.debug("Set tree assets changed")
self.__class__.set_changed(t=t, tp="assets")
def get(self):
if self.tree_has_changed():
self.renew()
return self.tree
if self.assets_has_changed():
self.tree.init_assets()
return self.tree
def renew(self):
new_obj = self.__class__.new(self.org_id)
self.tree = new_obj.tree
self.created_time = new_obj.created_time
self.assets_created_time = new_obj.assets_created_time
@classmethod
def new(cls, org_id=None):
from ..utils import TreeService
logger.debug("Create node tree")
if not org_id:
org_id = current_org.id
with tmp_to_org(org_id):
tree = TreeService.new()
obj = cls(tree, org_id)
obj.tree = tree
return obj
class TreeMixin:
_org_tree_map = {}
@classmethod
def tree(cls):
org_id = current_org.org_id()
t = cls.get_local_tree_cache(org_id)
if t is None:
t = TreeCache.new()
cls._org_tree_map[org_id] = t
return t.get()
@classmethod
def get_local_tree_cache(cls, org_id=None):
t = cls._org_tree_map.get(org_id)
return t
@classmethod
def refresh_tree(cls, t=None):
TreeCache.set_changed(tp="tree", t=t, org_id=current_org.id)
@classmethod
def refresh_node_assets(cls, t=None):
TreeCache.set_changed(tp="assets", t=t, org_id=current_org.id)
class FamilyMixin:
__parents = None
__children = None
__all_children = None
is_node = True
@staticmethod
def clean_children_keys(nodes_keys):
nodes_keys = sorted(list(nodes_keys), key=lambda x: (len(x), x))
nodes_keys_clean = []
for key in nodes_keys[::-1]:
found = False
for k in nodes_keys:
if key.startswith(k + ':'):
found = True
break
if not found:
nodes_keys_clean.append(key)
return nodes_keys_clean
@classmethod
def get_node_all_children_key_pattern(cls, key, with_self=True):
pattern = r'^{0}:'.format(key)
if with_self:
pattern += r'|^{0}$'.format(key)
return pattern
@classmethod
def get_node_children_key_pattern(cls, key, with_self=True):
pattern = r'^{0}:[0-9]+$'.format(key)
if with_self:
pattern += r'|^{0}$'.format(key)
return pattern
def get_children_key_pattern(self, with_self=False):
return self.get_node_children_key_pattern(self.key, with_self=with_self)
def get_all_children_pattern(self, with_self=False):
return self.get_node_all_children_key_pattern(self.key, with_self=with_self)
def is_children(self, other):
children_pattern = other.get_children_key_pattern(with_self=False)
return re.match(children_pattern, self.key)
def get_children(self, with_self=False):
pattern = self.get_children_key_pattern(with_self=with_self)
return Node.objects.filter(key__regex=pattern)
def get_all_children(self, with_self=False):
pattern = self.get_all_children_pattern(with_self=with_self)
children = Node.objects.filter(key__regex=pattern)
return children
@property
def children(self):
return self.get_children(with_self=False)
@property
def all_children(self):
return self.get_all_children(with_self=False)
def create_child(self, value, _id=None):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(
id=_id, key=child_key, value=value
)
return child
def get_next_child_key(self):
mark = self.child_mark
self.child_mark += 1
self.save()
return "{}:{}".format(self.key, mark)
def get_next_child_preset_name(self):
name = ugettext("New node")
values = [
child.value[child.value.rfind(' '):]
for child in self.get_children()
if child.value.startswith(name)
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values) + 1 if values else 1
return '{} {}'.format(name, count)
# Parents
@classmethod
def get_node_ancestor_keys(cls, key, with_self=False):
parent_keys = []
key_list = key.split(":")
if not with_self:
key_list.pop()
for i in range(len(key_list)):
parent_keys.append(":".join(key_list))
key_list.pop()
return parent_keys
def get_ancestor_keys(self, with_self=False):
return self.get_node_ancestor_keys(
self.key, with_self=with_self
)
@property
def ancestors(self):
return self.get_ancestors(with_self=False)
def get_ancestors(self, with_self=False):
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
return self.__class__.objects.filter(key__in=ancestor_keys)
@property
def parent_key(self):
parent_key = ":".join(self.key.split(":")[:-1])
return parent_key
def is_parent(self, other):
return other.is_children(self)
@property
def parent(self):
if self.is_org_root():
return self
parent_key = self.parent_key
return Node.objects.get(key=parent_key)
@parent.setter
def parent(self, parent):
if not self.is_node:
self.key = parent.key + ':fake'
return
children = self.get_all_children()
old_key = self.key
with transaction.atomic():
self.key = parent.get_next_child_key()
self.save()
for child in children:
child.key = child.key.replace(old_key, self.key, 1)
child.save()
def get_siblings(self, with_self=False):
key = ':'.join(self.key.split(':')[:-1])
pattern = r'^{}:[0-9]+$'.format(key)
sibling = Node.objects.filter(
key__regex=pattern.format(self.key)
)
if not with_self:
sibling = sibling.exclude(key=self.key)
return sibling
def get_family(self):
ancestors = self.get_ancestors()
children = self.get_all_children()
return [*tuple(ancestors), self, *tuple(children)]
class FullValueMixin:
key = ''
@lazyproperty
def full_value(self):
if self.is_org_root():
return self.value
value = self.tree().get_node_full_tag(self.key)
return value
class NodeAssetsMixin:
key = ''
id = None
@lazyproperty
def assets_amount(self):
amount = self.tree().assets_amount(self.key)
return amount
def get_all_assets(self):
from .asset import Asset
if self.is_org_root():
return Asset.objects.filter(org_id=self.org_id)
pattern = '^{0}$|^{0}:'.format(self.key)
return Asset.objects.filter(nodes__key__regex=pattern).distinct()
def get_assets(self):
from .asset import Asset
if self.is_org_root():
assets = Asset.objects.filter(Q(nodes=self) | Q(nodes__isnull=True))
else:
assets = Asset.objects.filter(nodes=self)
return assets.distinct()
def get_valid_assets(self):
return self.get_assets().valid()
def get_all_valid_assets(self):
return self.get_all_assets().valid()
@classmethod
def _get_nodes_all_assets(cls, nodes_keys):
"""
当节点比较多的时候,这种正则方式性能差极了
:param nodes_keys:
:return:
"""
from .asset import Asset
nodes_keys = cls.clean_children_keys(nodes_keys)
nodes_children_pattern = set()
for key in nodes_keys:
children_pattern = cls.get_node_all_children_key_pattern(key)
nodes_children_pattern.add(children_pattern)
pattern = '|'.join(nodes_children_pattern)
return Asset.objects.filter(nodes__key__regex=pattern).distinct()
@classmethod
def get_nodes_all_assets_ids(cls, nodes_keys):
nodes_keys = cls.clean_children_keys(nodes_keys)
assets_ids = set()
for key in nodes_keys:
node_assets_ids = cls.tree().all_assets(key)
assets_ids.update(set(node_assets_ids))
return assets_ids
@classmethod
def get_nodes_all_assets(cls, nodes_keys, extra_assets_ids=None):
from .asset import Asset
nodes_keys = cls.clean_children_keys(nodes_keys)
assets_ids = cls.get_nodes_all_assets_ids(nodes_keys)
if extra_assets_ids:
assets_ids.update(set(extra_assets_ids))
return Asset.objects.filter(id__in=assets_ids)
class SomeNodesMixin:
key = ''
default_key = '1'
default_value = 'Default'
ungrouped_key = '-10'
ungrouped_value = _('ungrouped')
empty_key = '-11'
empty_value = _("empty")
favorite_key = '-12'
favorite_value = _("favorite")
def is_default_node(self):
return self.key == self.default_key
def is_org_root(self):
if self.key.isdigit():
return True
else:
return False
@classmethod
def get_next_org_root_node_key(cls):
with tmp_to_org(Organization.root()):
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
if not org_nodes_roots_keys:
org_nodes_roots_keys = ['1']
max_key = max([int(k) for k in org_nodes_roots_keys])
key = str(max_key + 1) if max_key != 0 else '2'
return key
@classmethod
def create_org_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
ori_org = get_current_org()
with transaction.atomic():
if not ori_org.is_real():
return cls.default_node()
key = cls.get_next_org_root_node_key()
root = cls.objects.create(key=key, value=ori_org.name)
return root
@classmethod
def org_root(cls):
root = cls.objects.filter(key__regex=r'^[0-9]+$')
if root:
return root[0]
else:
return cls.create_org_root_node()
@classmethod
def ungrouped_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.ungrouped_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.ungrouped_key
)
return obj
@classmethod
def default_node(cls):
with tmp_to_org(Organization.default()):
defaults = {'value': cls.default_value}
try:
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.default_key,
)
except IntegrityError as e:
logger.error("Create default node failed: {}".format(e))
cls.modify_other_org_root_node_key()
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.default_key,
)
return obj
@classmethod
def favorite_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.favorite_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.favorite_key
)
return obj
@classmethod
def initial_some_nodes(cls):
cls.default_node()
cls.ungrouped_node()
cls.favorite_node()
@classmethod
def modify_other_org_root_node_key(cls):
"""
解决创建 default 节点失败的问题,
因为在其他组织下存在 default 节点,故在 DEFAULT 组织下 get 不到 create 失败
"""
logger.info("Modify other org root node key")
with tmp_to_org(Organization.root()):
node_key1 = cls.objects.filter(key='1').first()
if not node_key1:
logger.info("Not found node that `key` = 1")
return
if not node_key1.org.is_real():
logger.info("Org is not real for node that `key` = 1")
return
with transaction.atomic():
with tmp_to_org(node_key1.org):
org_root_node_new_key = cls.get_next_org_root_node_key()
for n in cls.objects.all():
old_key = n.key
key_list = n.key.split(':')
key_list[0] = org_root_node_new_key
new_key = ':'.join(key_list)
n.key = new_key
n.save()
logger.info('Modify key ( {} > {} )'.format(old_key, new_key))
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value"))
child_mark = models.IntegerField(default=0)
date_create = models.DateTimeField(auto_now_add=True)
objects = OrgManager.from_queryset(NodeQuerySet)()
is_node = True
_parents = None
class Meta:
verbose_name = _("Node")
ordering = ['key']
def __str__(self):
return self.value
# def __eq__(self, other):
# if not other:
# return False
# return self.id == other.id
#
def __gt__(self, other):
self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')]
self_parent_key = self_key[:-1]
other_parent_key = other_key[:-1]
if self_parent_key and self_parent_key == other_parent_key:
return self.value > other.value
return self_key > other_key
def __lt__(self, other):
return not self.__gt__(other)
@property
def name(self):
return self.value
@property
def level(self):
return len(self.key.split(':'))
@classmethod
def refresh_nodes(cls):
cls.refresh_tree()
@classmethod
def refresh_assets(cls):
cls.refresh_node_assets()
def as_tree_node(self):
from common.tree import TreeNode
name = '{} ({})'.format(self.value, self.assets_amount)
data = {
'id': self.key,
'name': name,
'title': name,
'pId': self.parent_key,
'isParent': True,
'open': self.is_org_root(),
'meta': {
'node': {
"id": self.id,
"name": self.name,
"value": self.value,
"key": self.key,
"assets_amount": self.assets_amount,
},
'type': 'node'
}
}
tree_node = TreeNode(**data)
return tree_node
def has_children_or_has_assets(self):
if self.children or self.get_assets().exists():
return True
return False
def delete(self, using=None, keep_parents=False):
if self.has_children_or_has_assets():
return
return super().delete(using=using, keep_parents=keep_parents)
@classmethod
def generate_fake(cls, count=100):
import random
org = get_current_org()
if not org or not org.is_real():
Organization.default().change_to()
nodes = list(cls.objects.all())
if count > 100:
length = 100
else:
length = count
for i in range(length):
node = random.choice(nodes)
child = node.create_child('Node {}'.format(i))
print("{}. {}".format(i, child))

View File

@@ -1,215 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
from common.utils import signer
from .base import BaseUser
from .asset import Asset
__all__ = ['AdminUser', 'SystemUser']
logger = logging.getLogger(__name__)
class AdminUser(BaseUser):
"""
A privileged user that ansible can use it to push system user and so on
"""
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', blank=True, max_length=128)
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
_prefer = "admin_user"
def __str__(self):
return self.name
@property
def become_pass(self):
password = signer.unsign(self._become_pass)
if password:
return password
else:
return ""
@become_pass.setter
def become_pass(self, password):
self._become_pass = signer.sign(password)
@property
def become_info(self):
if self.become:
info = {
"method": self.become_method,
"user": self.become_user,
"pass": self.become_pass,
}
else:
info = None
return info
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("Admin user")
@classmethod
def generate_fake(cls, count=10):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
obj = cls(name=forgery_py.name.full_name(),
username=forgery_py.internet.user_name(),
password=forgery_py.lorem_ipsum.word(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
obj.save()
logger.debug('Generate fake asset group: %s' % obj.name)
except IntegrityError:
print('Error continue')
continue
class SystemUser(BaseUser):
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_MYSQL = 'mysql'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet'),
(PROTOCOL_VNC, 'vnc'),
(PROTOCOL_MYSQL, 'mysql'),
)
LOGIN_AUTO = 'auto'
LOGIN_MANUAL = 'manual'
LOGIN_MODE_CHOICES = (
(LOGIN_AUTO, _('Automatic login')),
(LOGIN_MANUAL, _('Manually login'))
)
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
_prefer = 'system_user'
def __str__(self):
username = self.username
if self.username_same_with_user:
username = 'dynamic'
return '{0.name}({1})'.format(self, username)
def get_username(self):
if self.username_same_with_user:
return list(self.users.values_list('username', flat=True))
else:
return self.username
@property
def nodes_amount(self):
return self.nodes.all().count()
@property
def login_mode_display(self):
return self.get_login_mode_display()
def is_need_push(self):
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
return True
else:
return False
@property
def is_need_cmd_filter(self):
return self.protocol not in [self.PROTOCOL_RDP, self.PROTOCOL_VNC]
@property
def is_need_test_asset_connective(self):
return self.protocol not in [self.PROTOCOL_MYSQL]
@property
def can_perm_to_asset(self):
return self.protocol not in [self.PROTOCOL_MYSQL]
def _merge_auth(self, other):
super()._merge_auth(other)
if self.username_same_with_user:
self.username = other.username
@property
def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule
rules = CommandFilterRule.objects.filter(
filter__in=self.cmd_filters.all()
).distinct()
return rules
def is_command_can_run(self, command):
for rule in self.cmd_filter_rules:
action, matched_cmd = rule.match(command)
if action == rule.ACTION_ALLOW:
return True, None
elif action == rule.ACTION_DENY:
return False, matched_cmd
return True, None
def get_all_assets(self):
from assets.models import Node
nodes_keys = self.nodes.all().values_list('key', flat=True)
assets_ids = set(self.assets.all().values_list('id', flat=True))
nodes_assets_ids = Node.get_nodes_all_assets_ids(nodes_keys)
assets_ids.update(nodes_assets_ids)
assets = Asset.objects.filter(id__in=assets_ids)
return assets
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("System user")
@classmethod
def generate_fake(cls, count=10):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
obj = cls(name=forgery_py.name.full_name(),
username=forgery_py.internet.user_name(),
password=forgery_py.lorem_ipsum.word(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
obj.save()
logger.debug('Generate fake asset group: %s' % obj.name)
except IntegrityError:
print('Error continue')
continue

View File

@@ -1,108 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from django.utils import timezone
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from common.utils import validate_ssh_private_key
__all__ = [
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
]
def init_model():
from . import SystemUser, AdminUser, Asset
for cls in [SystemUser, AdminUser, Asset]:
if hasattr(cls, 'initial'):
cls.initial()
def generate_fake():
from . import SystemUser, AdminUser, Asset
for cls in [SystemUser, AdminUser, Asset]:
if hasattr(cls, 'generate_fake'):
cls.generate_fake()
def private_key_validator(value):
if not validate_ssh_private_key(value):
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
class Connectivity:
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
status = UNKNOWN
datetime = timezone.now()
def __init__(self, status, datetime):
self.status = status
self.datetime = datetime
def display(self):
return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.status)
def is_reachable(self):
return self.status == self.REACHABLE
def is_unreachable(self):
return self.status == self.UNREACHABLE
def is_unknown(self):
return self.status == self.UNKNOWN
@classmethod
def unreachable(cls):
return cls(cls.UNREACHABLE, timezone.now())
@classmethod
def reachable(cls):
return cls(cls.REACHABLE, timezone.now())
@classmethod
def unknown(cls):
return cls(cls.UNKNOWN, timezone.now())
@classmethod
def set(cls, key, value, ttl=None):
cache.set(key, value, ttl)
@classmethod
def get(cls, key):
value = cache.get(key, cls.unknown())
if not isinstance(value, cls):
value = cls.unknown()
return value
@classmethod
def set_unreachable(cls, key, ttl=0):
cls.set(key, cls.unreachable(), ttl)
@classmethod
def set_reachable(cls, key, ttl=0):
cls.set(key, cls.reachable(), ttl)
def __eq__(self, other):
return self.status == other.status
def __gt__(self, other):
return self.status > other.status
def __lt__(self, other):
return not self.__gt__(other)
def __str__(self):
return self.display()

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