From ffa9572804e6516769dc5ea514577ee7cc2e5e01 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 20 Apr 2020 17:43:25 +0800 Subject: [PATCH] first commit --- .gitignore | 1 + README.md | 16 + client/README.md | 5 + client/go.mod | 9 + client/go.sum | 30 ++ client/jumpcli.go | 315 ++++++++++++++ client/jumpcli.svg | 1 + server/jumpserver.py | 910 ++++++++++++++++++++++++++++++++++++++++ server/manager_user.sh | 152 +++++++ server/proto.md | 169 ++++++++ server/requirements.txt | 3 + 11 files changed, 1611 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client/README.md create mode 100644 client/go.mod create mode 100644 client/go.sum create mode 100644 client/jumpcli.go create mode 100644 client/jumpcli.svg create mode 100644 server/jumpserver.py create mode 100644 server/manager_user.sh create mode 100644 server/proto.md create mode 100644 server/requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d00477 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +jumpserver \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..40f50b6 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# 跳板机管理平台 + +代码目录结构如下: + +├── README.md +├── client +│ ├── README.md +│ ├── go.mod +│ ├── go.sum +│ ├── jumpcli.go +│ └── jumpcli.svg +└── server + ├── jumpserver.py + ├── manager_user.sh + ├── proto.md + └── requirements.txt diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..c6bea82 --- /dev/null +++ b/client/README.md @@ -0,0 +1,5 @@ +## 跳板机客户机使用 + +动态教程 + +[![asciicast](https://asciinema.org/a/nVxZuzht0o5tk179P3hDhIYur.svg)](https://asciinema.org/a/nVxZuzht0o5tk179P3hDhIYur) \ No newline at end of file diff --git a/client/go.mod b/client/go.mod new file mode 100644 index 0000000..6666cff --- /dev/null +++ b/client/go.mod @@ -0,0 +1,9 @@ +module cynking.com/jumpserver + +go 1.14 + +require ( + github.com/manifoldco/promptui v0.7.0 + github.com/mattn/go-sqlite3 v2.0.3+incompatible + golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 +) diff --git a/client/go.sum b/client/go.sum new file mode 100644 index 0000000..d67eb19 --- /dev/null +++ b/client/go.sum @@ -0,0 +1,30 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4= +github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/client/jumpcli.go b/client/jumpcli.go new file mode 100644 index 0000000..5677144 --- /dev/null +++ b/client/jumpcli.go @@ -0,0 +1,315 @@ +package main + +import ( + "database/sql" + "fmt" + "io" + "io/ioutil" + "os" + "os/signal" + "os/user" + "runtime" + "strconv" + "strings" + "syscall" + "time" + + "github.com/manifoldco/promptui" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" +) + +var ( + DarwinAuthFileFmt = "/Users/%s/.ssh/id_rsa" + LinuxAuthFileFmt = "/home/%s/.ssh/id_rsa" + AuthFileFmt = LinuxAuthFileFmt + DatabaseFile = "/usr/local/jumpserver/jumpserver.db" + QuitSelect = "quit" +) + +type SSHTerminal struct { + Session *ssh.Session + exitMsg string + stdout io.Reader + stdin io.Writer + stderr io.Reader +} + +// ssh server +type HostServer struct { + Name string + Host string + Port int +} + +// SSHUser ssh user +type SSHUser struct { + Username string + Password string + IdentityFile string +} + +func init() { + if runtime.GOOS == "linux" { + AuthFileFmt = LinuxAuthFileFmt + } else if runtime.GOOS == "darwin" { + AuthFileFmt = DarwinAuthFileFmt + } else { + fmt.Printf("program not support os(%s)\n", runtime.GOOS) + } +} + +func (t *SSHTerminal) updateTerminalSize() { + go func() { + // 窗口大小改变 + sigwinchCh := make(chan os.Signal, 1) + signal.Notify(sigwinchCh, syscall.SIGWINCH) + + fd := int(os.Stdin.Fd()) + termWidth, termHeight, err := terminal.GetSize(fd) + if err != nil { + fmt.Println(err) + } + + for { + select { + case sigwinch := <-sigwinchCh: + if sigwinch == nil { + return + } + currTermWidth, currTermHeight, err := terminal.GetSize(fd) + + // 窗口没有发生改变 + if currTermHeight == termHeight && currTermWidth == termWidth { + continue + } + + t.Session.WindowChange(currTermHeight, currTermWidth) + if err != nil { + fmt.Printf("Unable to send window-change reqest: %s.", err) + continue + } + + termWidth, termHeight = currTermWidth, currTermHeight + + } + } + }() + +} + +func (t *SSHTerminal) interactiveSession() error { + defer func() { + if t.exitMsg == "" { + fmt.Fprintln(os.Stdout, "the connection was closed on the remote side on ", time.Now().Format(time.RFC822)) + } else { + fmt.Fprintln(os.Stdout, t.exitMsg) + } + }() + + fd := int(os.Stdin.Fd()) + state, err := terminal.MakeRaw(fd) + if err != nil { + return err + } + defer terminal.Restore(fd, state) + + termWidth, termHeight, err := terminal.GetSize(fd) + if err != nil { + return err + } + + termType := os.Getenv("TERM") + if termType == "" { + termType = "xterm-256color" + } + + err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + }) + if err != nil { + return err + } + + t.updateTerminalSize() + + t.stdin, err = t.Session.StdinPipe() + if err != nil { + return err + } + t.stdout, err = t.Session.StdoutPipe() + if err != nil { + return err + } + t.stderr, err = t.Session.StderrPipe() + + go io.Copy(os.Stderr, t.stderr) + go io.Copy(os.Stdout, t.stdout) + go func() { + buf := make([]byte, 128) + for { + n, err := os.Stdin.Read(buf) + if err != nil { + fmt.Println(err) + return + } + if n > 0 { + _, err = t.stdin.Write(buf[:n]) + if err != nil { + fmt.Println(err) + t.exitMsg = err.Error() + return + } + } + } + }() + + err = t.Session.Shell() + if err != nil { + return err + } + err = t.Session.Wait() + if err != nil { + return err + } + return nil +} + +func NewTerminal(server *HostServer, sshUser *SSHUser) (err error) { + key, err := ioutil.ReadFile(fmt.Sprintf(AuthFileFmt, sshUser.Username)) + if err != nil { + return + } + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + return + } + + sshConfig := &ssh.ClientConfig{ + User: sshUser.Username, + Auth: []ssh.AuthMethod{ + ssh.Password(sshUser.Password), + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", server.Host, server.Port), sshConfig) + if err != nil { + fmt.Println(err) + } + defer client.Close() + + session, err := client.NewSession() + if err != nil { + return err + } + defer session.Close() + + s := SSHTerminal{ + Session: session, + } + return s.interactiveSession() +} + +func handlerSignal() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + return + case syscall.SIGHUP: + default: + return + } + } +} + +func main() { + // 获取当前用户 + userInfo, err := user.Current() + if err != nil { + fmt.Println("user.Current failed!") + return + } + + // 连接数据库 + db, err := sql.Open("sqlite3", DatabaseFile) + if err != nil { + fmt.Printf("open database failed! err=%v", err) + return + } + + // 查询当前用户有访问权限的主机 + rows, err := db.Query(fmt.Sprintf("SELECT name, ip, port FROM hosts where isdelete=0 and name in(select hostname from hostuser where username='%s');", userInfo.Username)) + if err != nil { + fmt.Printf("db.Query failed! err=%v", err) + return + } + + // ui展示列表 + menuLabels := make([]string, 0) + menuLabels = append(menuLabels, QuitSelect) // 退出 + for rows.Next() { + var ( + name string + ip string + port int + ) + err = rows.Scan(&name, &ip, &port) + if err != nil { + fmt.Printf("rows.Scan failed! err=%v", err) + continue + } + + menuLabels = append(menuLabels, fmt.Sprintf("%s:%s:%d", name, ip, port)) + } + rows.Close() + db.Close() + + // 选项列表 + prompt := promptui.Select{ + Label: "Select Host (quit for ctrl+c or select quit)", + Items: menuLabels, + Size: len(menuLabels), + } + + for { + _, selectLabel, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", selectLabel) + if selectLabel == QuitSelect { + return + } + + // 获取选中信息 + hostItems := strings.Split(selectLabel, ":") + if len(hostItems) != 3 { + fmt.Printf("invalid ssh host: %s", selectLabel) + continue + } + port, err := strconv.ParseInt(hostItems[2], 10, 32) + if err != nil { + fmt.Printf("invalid ssh port: %s", selectLabel[2]) + continue + } + server := &HostServer{Name: hostItems[0], Host: hostItems[1], Port: int(port)} + user := &SSHUser{Username: userInfo.Username, IdentityFile: fmt.Sprintf(AuthFileFmt, userInfo.Username)} + + // 新建一个终端 + err = NewTerminal(server, user) + if err != nil { + fmt.Printf("NewTerminal err=%v\n", err) + } + } +} diff --git a/client/jumpcli.svg b/client/jumpcli.svg new file mode 100644 index 0000000..515a834 --- /dev/null +++ b/client/jumpcli.svg @@ -0,0 +1 @@ +~#ssh登录跳板机登录跳板机~ckqasLastlogin:WedApr115:54:152020from172.20.1.73╭─danielatcppserverin~[15:56:40]╰─✗#执行jumpserver,选择需要登录的主机录的主╭─danielatcppserverin~[15:56:46]╰─✗jumpserverUsethearrowkeystonavigate:?SelectHost(quitforctrl+corselectquit):quitckqas129:192.168.1.129:22ckqas129:192.168.1.129:22Youchoose"ckqas129:192.168.1.129:22"Lastlogin:WedApr115:54:312020from192.168.1.44#daniel@phpserverin~[15:56:53]$$lsgopubgoredisredis.logssl#daniel@phpserverin~[15:56:54]$exittheconnectionwasclosedontheremotesideon01Apr2015:56CSTNewTerminalerr=<nil>quitYouchoose"quit"╭─danielatcppserverin~[15:57:00]#退出跳板机出跳板╭─danielatcppserverin~[15:57:07]Connectionto192.168.1.44closed.~exit~# ssh登录跳板机~c~ck~ckq~ckqa╰─✗#执行jumpserver╰─✗#执行jumpserver,选择需要登╰─✗#执行jumpserver,选择需要登录的╰─✗#执行jumpserver,选择需要登录的主╰─✗jumpserver╰─✗jumpserver╰─✗jumpserver╰─✗jumpserverquitckqas129:192.168.1.129:22$l$e$ex$exiEOF╰─✗#╰─✗#退╰─✗#退出╰─✗#退出跳╰─✗exit╰─✗exit╰─✗exit╰─✗exit~e~ex~exi \ No newline at end of file diff --git a/server/jumpserver.py b/server/jumpserver.py new file mode 100644 index 0000000..e74eef6 --- /dev/null +++ b/server/jumpserver.py @@ -0,0 +1,910 @@ +# -*- coding: UTF-8 -*- + +import sys +import getopt +import os +import time +from threading import Timer +import subprocess +import sqlite3 +import json +import urllib.parse as urlparse +from urllib.parse import urlencode +import hashlib +import requests + +from flask import request, Flask, redirect, session, render_template +from flask_cors import CORS + +app = Flask(__name__, + static_folder="/home/daniel/vue-admin/dist/static", + template_folder="/home/daniel/vue-admin/dist") +app.config['SECRET_KEY'] = os.urandom(24) +CORS(app) + +# http bind +gHost = "" +gPort = 0 +gDebug = 0 + +# 默认ssh管理账号 +gDefaultSSHAdmin = "ec2-user" + +# 默认初始密码 +gDefaultInitPassword = "cynking" + +# sql语句 +gUsersTableSql = "create table if not exists users(id integer primary key autoincrement, name varchar(128) not null UNIQUE, sudo integer not null default 0, isdelete integer not null default 0, desc varchar(128), date timestamp not null default (datetime('now','localtime')))" +gHostsTableSql = "create table if not exists hosts(id integer primary key autoincrement, name varchar(128) not null UNIQUE, ip varchar(32) not null, port integer not null default 22, isdelete integer not null default 0, desc varchar(128), date timestamp not null default (datetime('now','localtime')))" +gHostUserSql = "create table if not exists hostuser(id integer primary key autoincrement, hostname varchar(128) not null, username varchar(128) not null, sudo integer not null default 0, isdelete integer not null default 0, date timestamp not null default (datetime('now','localtime')))" + +# 添加跳板机用户脚本 +gManagerUserShellFile = "manager_user.sh" +# 添加远程用户脚本 +# gAddLocalUserShellFile = "add_remote_user.sh" + +# sso应用信息 +SSO_APPID = 18 +SSO_APPTOKEN = "b813d5b7e4449177cd844bfb00ba8108" +SSO_URL = URL = "http://sso.cynking.cn/" +SSO_AUTH_URL = "http://sso.cynking.cn/?do=login.s_login&appid=%d" % SSO_APPID + +# 服务主页地址 +gUrl = "http://192.168.1.44:8080/" +# sso管理 +gSsoManager = {} +# sso定时器执行周期 +SSO_TIMER_PERIOD = 5*60 +# sso过期时间 +SSO_EXPIRE_TIMEOUT = 24*60*60 + + +# 解析命令行 +def parse_cmd(): + ''' 解析命令行参数 ''' + global gHost, gPort, gDebug + try: + opts, _ = getopt.getopt(sys.argv[1:], "Hh:p:l:d:", [ + "ip=", "port=", "debug="]) + except getopt.GetoptError: + print('python3 %s -h -p -d ' % (sys.argv[0])) + sys.exit(0) + for opt, arg in opts: + if opt == '-H': + print('python3 %s -h -p -d ' % (sys.argv[0])) + sys.exit(0) + elif opt in ("-h", "--host"): + gHost = arg + elif opt in ("-p", "--port"): + gPort = int(arg) + elif opt in ("-l", "--debug"): + gDebug = int(arg) + + if len(gHost) == 0 or gPort == 0: + print('python3 %s -h -p -d ' % (sys.argv[0])) + sys.exit(0) + + # 主页url + global gUrl + gUrl = "http://%s:%d/" % (gHost, gPort) + + +# 只有执行结果 +def exec_command(cmd): + return subprocess.call(cmd, shell=True) + + +# 执行结果 和 输出 +def exec_command_output(cmd): + return subprocess.getstatusoutput(cmd) + + +# 同步目标机sh文件 +def sync_remote_control_file(host, port): + status, output = exec_command_output( + "scp -P %d manager_user.sh %s@%s:.manager_user.sh" % (port, gDefaultSSHAdmin, host)) + if status != 0: + print("sync_remote_control_file error %s" % output) + return -1 + return 0 + + +# 重定向 +def redirect_sso(): + return '' + + +# 计算md5 +def make_md5(data): + md = hashlib.md5() + md.update(data.encode('utf-8')) + return md.hexdigest() + + +# 获取用户信息 +def getUserInfo(params): + params["do"] = "user.getInfo" + params["appid"] = SSO_APPID + params["_time"] = int(time.time()) + sort_params = sorted(params.items()) + + params = {} + for value in sort_params: + params[value[0]] = value[1] + + params["sig"] = make_md5(urlencode(params) + SSO_APPTOKEN) # 签名 + url = SSO_URL + "cmsapi.php" + + resp = requests.get(url, params) + return json.loads(resp.text) + + +def checkCookie(request): + # 获取http参数 + if request.method == "GET": + sso_uid = 0 + _sso_uid = request.args.get('sso_uid') + if _sso_uid != None: + sso_uid = int(_sso_uid) + sso_token = request.args.get('sso_token') + elif request.method == "POST": + sso_uid = 0 + _sso_uid = request.form.get("sso_uid") + if _sso_uid != None: + sso_uid = int(_sso_uid) + sso_token = request.form.get("sso_token") + + # 获取sso信息有就保存 + if sso_uid != None and sso_token != None and sso_uid != 0 and len(sso_token) > 0: + session["sso_uid"] = sso_uid + session["sso_token"] = sso_token + + # 从session中获取sso信息 + ck_uid = session.get("sso_uid") + ck_token = session.get("sso_token") + if ck_uid == None or ck_token == None or ck_uid == 0 or len(ck_token) == 0: + return False + + # sso信息未过期则不需要验证 + sso_info = gSsoManager.get(ck_uid) + if sso_info != None: + if sso_info["sso_token"] == ck_token: + return True + return False + + # # 丢弃 + # # 每次请求都验证用户信息太慢了 + # params = {"sso_uid": ck_uid, "sso_token": ck_token} + # rets = getUserInfo(params) + + # if rets == None: + # return False + # if rets["ret"] == -200: + # print(u"不信任的主机,请添加白名单") + # return False + # if rets["ret"] != 1: + # return False + # rets_data = rets.get("data") + # if rets_data == None: + # return False + # rets_data_uid = rets_data.get("uid") + # if rets_data_uid == None: + # return False + # return True + + +# sso回调接口 +@app.route('/sso', methods=['GET', 'POST']) +def sso(): + return do_sso(request) + + +def do_sso(request): + if request.method == "GET": + sso_uid = int(request.args.get('sso_uid')) + sso_token = request.args.get('sso_token') + elif request.method == "POST": + sso_uid = int(request.form.get("sso_uid")) + sso_token = request.form.get("sso_token") + + # 记录sso信息 + gSsoManager[sso_uid] = { + "sso_uid": sso_uid, "sso_token": sso_token, "update_time": int(time.time())} + + params = urlparse.urlparse(request.url).query + return redirect(gUrl + "?" + params) + + +# 登录 +@app.route('/login', methods=['GET', 'POST']) +def login(): + if not checkCookie(request): + return redirect_sso() + return do_login(request) + + +# 主页 +@app.route('/', methods=['GET', 'POST']) +def index(): + if not checkCookie(request): + return redirect_sso() + return render_template("index.html") + + +# 控制 +@app.route('/jump/hosts', methods=['GET', 'POST']) +def hostlist(): + if not checkCookie(request): + return redirect_sso() + return do_hostlist(request) + + +@app.route('/jump/users', methods=['GET', 'POST']) +def userlist(): + if not checkCookie(request): + return redirect_sso() + return do_userlist(request) + + +@app.route('/jump/user/add', methods=['GET', 'POST']) +def add_user(): + if not checkCookie(request): + return redirect_sso() + return do_add_user(request) + + +@app.route('/jump/user/del', methods=['GET', 'POST']) +def del_user(): + if not checkCookie(request): + return redirect_sso() + return do_del_user(request) + + +@app.route('/jump/user/sudo', methods=['GET', 'POST']) +def sudo_user(): + if not checkCookie(request): + return redirect_sso() + return do_sudo_user(request) + + +@app.route('/jump/user/unsudo', methods=['GET', 'POST']) +def unsudo_user(): + if not checkCookie(request): + return redirect_sso() + return do_unsudo_user(request) + + +@app.route('/jump/user/modify', methods=['GET', 'POST']) +def modify_user(): + if not checkCookie(request): + return redirect_sso() + return do_modify_user(request) + + +@app.route('/jump/user/hosts', methods=['GET', 'POST']) +def userhostlist(): + if not checkCookie(request): + return redirect_sso() + return do_userhostlist(request) + + +@app.route('/jump/host/add', methods=['GET', 'POST']) +def add_host(): + if not checkCookie(request): + return redirect_sso() + return do_add_host(request) + + +@app.route('/jump/host/del', methods=['GET', 'POST']) +def del_host(): + if not checkCookie(request): + return redirect_sso() + return do_del_host(request) + + +@app.route('/jump/host/adduser', methods=['GET', 'POST']) +def host_adduser(): + if not checkCookie(request): + return redirect_sso() + return do_host_adduser(request) + + +@app.route('/jump/host/deluser', methods=['GET', 'POST']) +def host_deluser(): + if not checkCookie(request): + return redirect_sso() + return do_host_deluser(request) + + +@app.route('/jump/host/sudouser', methods=['GET', 'POST']) +def host_sudouser(): + if not checkCookie(request): + return redirect_sso() + return do_host_sudouser(request) + + +@app.route('/jump/host/unsudouser', methods=['GET', 'POST']) +def host_unsudouser(): + if not checkCookie(request): + return redirect_sso() + return do_host_unsudouser(request) + + +@app.route('/jump/host/modifyuser', methods=['GET', 'POST']) +def host_modifyuser(): + if not checkCookie(request): + return redirect_sso() + return do_host_modifyuser(request) + + +@app.route('/jump/host/users', methods=['GET', 'POST']) +def hostuserlist(): + if not checkCookie(request): + return redirect_sso() + return do_hostuserlist(request) + + +@app.route('/jump/hostuser', methods=['GET', 'POST']) +def hostuser(): + if not checkCookie(request): + return redirect_sso() + return do_hostuserall(request) + + +def do_login(request): + if request.method == "GET": + username = request.args.get('username') + password = request.args.get('password') + elif request.method == "POST": + username = request.form.get("username") or "admin" + password = request.form.get("password") or "123456" + + print("login username:%s password:%s" % (username, password)) + resp = {} + resp["msg"] = "ok" + resp["code"] = 200 + resp["user"] = username + resp["sessionkey"] = "cookie-666" + return json.dumps(resp) + + +# get主机列表 +def do_hostlist(request): + conn, cur = get_db() + cur.execute("select id,name,ip,port,desc,date from hosts where isdelete=0;") + hosts = cur.fetchall() + cur.close() + conn.close() + + resp = [] + for host in hosts: + res = {} + res["id"] = host[0] + res["name"] = host[1] + res["ip"] = host[2] + res["port"] = host[3] + res["desc"] = host[4] + res["ctime"] = host[5] + resp.append(res) + return json.dumps(resp) + + +# 获取用户列表 +def do_userlist(request): + conn, cur = get_db() + cur.execute("select id,name,sudo,desc,date from users where isdelete=0;") + users = cur.fetchall() + cur.close() + conn.close() + + resp = [] + for user in users: + res = {} + res["id"] = user[0] + res["name"] = user[1] + res["sudo"] = user[2] + res["desc"] = user[3] + res["ctime"] = user[4] + resp.append(res) + return json.dumps(resp) + +# 添加用户 +def do_add_user(request): + if request.method == "GET": + name = request.args.get('name') + desc = request.args.get('desc') + elif request.method == "POST": + name = request.form["name"] + desc = request.form["desc"] + + conn, cur = get_db() + cur.execute("select count(1) from users where name='%s'" % name) + ret = cur.fetchone() + if (len(ret) > 0 and ret[0]) >= 1: + return "user %s exists" % name + + status, output = exec_command_output("sudo sh %s add %s \"%s\"" % ( + gManagerUserShellFile, name, gDefaultInitPassword)) + if status != 0: + print("output=%s" % output) + return "error %s" % output + + # 新增用户 sql + cur.execute("insert into users(name,desc) values('%s',\"%s\")" % + (name, desc)) + conn.commit() + cur.close() + conn.close() + return "ok" + + +# 删除用户 +def do_del_user(request): + if request.method == "GET": + username = request.args.get('name') + elif request.method == "POST": + username = request.form["name"] + else: + return "invalid request for del user" + + status, output = exec_command_output( + "sudo sh %s del %s" % (gManagerUserShellFile, username)) + if status != 0: + print("output=%s" % output) + return "error %s" % output + + conn, cur = get_db() + cur.execute("delete from users where name='%s'" % username) + cur.execute("delete from hostuser where username='%s'" % username) + conn.commit() + cur.close() + conn.close() + return "del user %s ok" % username + + +# 修改用户 +def do_modify_user(request): + if request.method == "GET": + username = request.args.get('username') + sudo = int(request.args.get('sudo')) + desc = request.args.get('desc') + elif request.method == "POST": + username = request.form["username"] + sudo = int(request.form["sudo"]) + desc = request.form["desc"] + else: + return "invalid request for user" + + # check param + if sudo != 0 and sudo != 1: + return "invalid request sudo:%d param" % sudo + + # 需求数据库 + conn, cur = get_db() + + # 检查 + cur.execute("select sudo,desc from users where name='%s'" % username) + users = cur.fetchall() + if len(users) == 0: + print("user(%s) not exitst" % username) + return "user(%s) not exitst" % username + + user = users[0] + user_sudo = user[0] + user_desc = user[1] + change = False # 是否需要修改 + opParam = "sudo" # 操作类型 sudo / unsudo + if sudo == 0: + opParam = "unsudo" + + if sudo != user_sudo: + change = True + + # 处理sudo权限 + status, output = exec_command_output( + "sudo sh manager_user.sh %s %s" % (opParam, username)) + if status != 0: + print("%s user user(%s) failed! => output=%s" % + (opParam, username, output)) + return "error: %s user user(%s) failed! => output=%s" % (opParam, username, output) + + if desc != user_desc: + change = True + + # 记录在数据库中 + if change: + print("update users set sudo=%d,desc=\"%s\" where name='%s'" % + (sudo, desc, username)) + cur.execute("update users set sudo=%d,desc=\"%s\" where name='%s'" % ( + sudo, desc, username)) + conn.commit() + + # 清理数据库 + cur.close() + conn.close() + print("modify user:%s successful [output: %s]" % (username, output)) + return "modify user:%s successful [output: %s]" % (username, output) + + +# 添加主机 +def do_add_host(request): + if request.method == "GET": + name = request.args.get('name') + ip = request.args.get('host') + port = int(request.args.get('port')) + desc = request.args.get('desc') + elif request.method == "POST": + name = request.form["name"] + ip = request.form["host"] + port = int(request.form["port"]) + desc = request.form["desc"] + + conn, cur = get_db() + cur.execute( + "select count(1) from hosts where name='%s' or ip='%s'" % (name, ip)) + ret = cur.fetchone() + if (len(ret) > 0 and ret[0]) >= 1: + return "alias name(%s) or ip(%s) is exists" % (name, ip) + + # 新增用户 sql + cur.execute("insert into hosts(name,ip,port,desc) values('%s','%s',%d,'%s')" % ( + name, ip, port, desc)) + cur.execute("insert into hostuser(hostname,username,sudo) values('%s','%s','%d')" % ( + name, gDefaultSSHAdmin, 1)) + conn.commit() + + cur.close() + conn.close() + return "add host %s:%s ok" % (name, ip) + + +# 删除主机 +def do_del_host(request): + if request.method == "GET": + hostname = request.args.get('name') + ip = request.args.get('host') + elif request.method == "POST": + hostname = request.form["name"] + ip = request.form["host"] + else: + return "invalid request for del host" + + conn, cur = get_db() + cur.execute("delete from hosts where name='%s' and ip='%s'" % + (hostname, ip)) + cur.execute("delete from hostuser where hostname='%s'" % hostname) + conn.commit() + + cur.close() + conn.close() + return "delete host %s:%s ok" % (hostname, ip) + + +# 主机上添加用户 +def do_host_adduser(request): + if request.method == "GET": + hostname = request.args.get('hostname') + username = request.args.get('username') + elif request.method == "POST": + hostname = request.form["hostname"] + username = request.form["username"] + else: + return "invalid request for add user to host" + + # 需求数据库 + conn, cur = get_db() + # 检查 + cur.execute("select count(1) from hostuser where hostname='%s' and username='%s'" % ( + hostname, username)) + ret = cur.fetchone() + if (len(ret) > 0 and ret[0]) >= 1: + print("user(%s) exitst on host(%s)" % (username, hostname)) + return "user(%s) exitst on host(%s)" % (username, hostname) + + # 检查 + cur.execute("select ip,port from hosts where name='%s'" % (hostname)) + hostips = cur.fetchone() + if hostips == None: + print("host(%s) not exitst on hosts" % hostname) + return "host(%s) not exitst on hosts" % hostname + hostip = hostips[0] + hostport = int(hostips[1]) + + # 同步控制文件 + sync_remote_control_file(hostip, hostport) + + # 获取公钥 + status, publicSshRsaKey = exec_command_output( + "sudo cat /home/%s/.ssh/id_rsa.pub" % username) + if status != 0: + print("cat user(%s) id_rsa.pub failed!", username) + return "error %s" % publicSshRsaKey + + print("ssh %s@%s -p%d sudo sh .manager_user.sh add %s %s \"\"%s\"\"" % + (gDefaultSSHAdmin, hostip, hostport, username, gDefaultInitPassword, publicSshRsaKey)) + # 主机上新建用户 + status, output = exec_command_output("ssh %s@%s -p%d sudo sh .manager_user.sh add %s %s \"\"%s\"\"" % ( + gDefaultSSHAdmin, hostip, hostport, username, gDefaultInitPassword, publicSshRsaKey)) + if status != 0: + print("remote add user host(%s) user(%s) failed! => output=%s" % + (hostname, username, output)) + return "error: remote add user host(%s) user(%s) failed! => output=%s" % (hostname, username, output) + + # 记录在数据库中 + cur.execute("insert into hostuser(hostname,username) values('%s','%s')" % ( + hostname, username)) + cur.fetchone() + conn.commit() + + # 清理数据库 + cur.close() + conn.close() + print("host remote =>> add user:%s to host:%s successful [output: %s]" % ( + username, hostname, output)) + return "host remote =>> add user:%s to host:%s successful" % (username, hostname) + + +# 主机上删除用户 +def do_host_deluser(request): + if request.method == "GET": + hostname = request.args.get('hostname') + username = request.args.get('username') + elif request.method == "POST": + hostname = request.form["hostname"] + username = request.form["username"] + else: + return "invalid request for add user to host" + + # 需求数据库 + conn, cur = get_db() + + # 检查 + cur.execute("select count(1) from hostuser where hostname='%s' and username='%s'" % ( + hostname, username)) + ret = cur.fetchone() + if (len(ret) > 0 and ret[0]) == 0: + print("user(%s) not exitst on host(%s)" % (username, hostname)) + return "user(%s) not exitst on host(%s)" % (username, hostname) + + # 检查 + cur.execute("select ip,port from hosts where name='%s'" % (hostname)) + hostips = cur.fetchone() + if hostips == None: + print("host(%s) not exitst on hosts" % hostname) + return "host(%s) not exitst on hosts" % hostname + hostip = hostips[0] + hostport = int(hostips[1]) + + # 同步控制文件 + sync_remote_control_file(hostip, hostport) + + # 主机上新建用户 + status, output = exec_command_output( + "ssh %s@%s -p%d sudo sh .manager_user.sh del %s" % (gDefaultSSHAdmin, hostip, hostport, username)) + if status != 0: + print("remote del user host(%s) user(%s) failed! => output=%s" % + (hostname, username, output)) + return "error: remote del user host(%s) user(%s) failed! => output=%s" % (hostname, username, output) + + # 记录在数据库中 + cur.execute("delete from hostuser where hostname='%s' and username='%s'" % ( + hostname, username)) + conn.commit() + + # 清理数据库 + cur.close() + conn.close() + print("host remote =>> del user:%s from host:%s successful [output: %s]" % ( + username, hostname, output)) + return "host remote =>> del user:%s from host:%s successful" % (username, hostname) + +# 主机上修改用户信息 +def do_host_modifyuser(request): + if request.method == "GET": + hostname = request.args.get('hostname') + username = request.args.get('username') + sudo = int(request.args.get('sudo')) + desc = request.args.get('desc') + elif request.method == "POST": + hostname = request.form["hostname"] + username = request.form["username"] + sudo = int(request.form["username"]) + desc = request.form["desc"] + else: + return "invalid request for add user to host" + + # 需求数据库 + conn, cur = get_db() + + # 检查 + cur.execute("select sudo,desc from hostuser where hostname='%s' and username='%s' and isdelete=0" % ( + hostname, username)) + hostusers = cur.fetchone() + if len(hostusers) > 0 and hostusers[0] != None: + print("user(%s) not exitst on host(%s)" % (username, hostname)) + return "user(%s) not exitst on host(%s)" % (username, hostname) + + hostuser = hostusers[0] + + user_sudo = hostuser[0] + user_desc = hostuser[1] + change = False + opParam = "sudo" + + if sudo == 0: + opParam = "unsudo" + + if sudo != user_sudo: + change = True + + # 检查 + cur.execute("select ip,port from hosts where name='%s'" % (hostname)) + hostips = cur.fetchone() + if hostips == None: + print("host(%s) not exitst on hosts" % hostname) + return "host(%s) not exitst on hosts" % hostname + + hostip = hostips[0] + hostport = int(hostips[1]) + + # 主机上修改sudo + status, output = exec_command_output( + "ssh %s@%s -p%d sudo sh .manager_user.sh %s %s" % (gDefaultSSHAdmin, hostip, hostport, opParam, username)) + if status != 0: + print("remote %s user host(%s) user(%s) failed! => output=%s" % + (opParam, hostname, username, output)) + return "error: remote %s user host(%s) user(%s) failed! => output=%s" % (opParam, hostname, username, output) + + if desc != user_desc: + change = True + + if change: + # 记录在数据库中 + cur.execute("update hostuser set sudo=%d,desc=%s where hostname='%s' and username='%s'" % ( + sudo, desc, hostname, username)) + conn.commit() + + # 清理数据库 + cur.close() + conn.close() + print("host remote =>> %s user:%s from host:%s successful [output: %s]" % ( + opParam, username, hostname, output)) + return "host remote =>> %s user:%s from host:%s successful [output: %s]" % (opParam, username, hostname, output) + + +# 获取用户所有的主机列表 +def do_userhostlist(request): + if request.method == "GET": + username = request.args.get('username') + elif request.method == "POST": + username = request.form["username"] + else: + return "invalid request for getting user host list" + + conn, cur = get_db() + cur.execute("select id,name,ip,port,desc,date from hosts where isdelete=0 and name in (select hostname from hostuser where username='%s')" % username) + hosts = cur.fetchall() + cur.close() + conn.close() + + resp = [] + for host in hosts: + res = {} + res["id"] = host[0] + res["name"] = host[1] + res["ip"] = host[2] + res["port"] = host[3] + res["desc"] = host[4] + res["ctime"] = host[5] + resp.append(res) + return json.dumps(resp) + + +# 获取主机上的用户列表 +def do_hostuserlist(request): + if request.method == "GET": + hostname = request.args.get('hostname') + elif request.method == "POST": + hostname = request.form["hostname"] + else: + return "invalid request for getting host user list" + + conn, cur = get_db() + cur.execute( + "select id,username,sudo,date from hostuser where isdelete=0 and hostname='%s'" % hostname) + users = cur.fetchall() + cur.close() + conn.close() + + resp = [] + for user in users: + res = {} + res["id"] = user[0] + res["username"] = user[1] + res["sudo"] = user[2] + res["ctime"] = user[3] + resp.append(res) + return json.dumps(resp) + + +# 获取所有的用户主机列表 +def do_hostuserall(request): + conn, cur = get_db() + cur.execute( + "select id,hostname,username,sudo,date from hostuser where isdelete=0") + users = cur.fetchall() + cur.close() + conn.close() + + resp = [] + for user in users: + res = {} + res["id"] = user[0] + res["hostname"] = user[1] + res["username"] = user[2] + res["sudo"] = user[3] + res["ctime"] = user[4] + resp.append(res) + return json.dumps(resp) + + +# 连接数据库 +def get_db(): + conn = sqlite3.connect('/usr/local/jumpserver/jumpserver.db') + curr = conn.cursor() + return conn, curr + + +# 初始化表 +def init_db(): + # 连接数据库 + conn, _ = get_db() + if conn == None: + sys.exit(1) + + # 初始化表 + conn.execute(gUsersTableSql) + conn.execute(gHostsTableSql) + # 创建主机数据库 + conn.execute(gHostUserSql) + + +# sso本地管理 +def sso_timeout(): + # 当前时间戳 + nowtime = int(time.time()) + # 需求删除的key + delkeys = [] + for sso_uid, sso_info in gSsoManager.items(): + if nowtime - sso_info["update_time"] >= SSO_EXPIRE_TIMEOUT: + delkeys.append(sso_uid) + + # 删除key + for key in delkeys: + del gSsoManager[key] + + # 循环定时器 + Timer(SSO_TIMER_PERIOD, sso_timeout).start() + + +def main(): + # 解析命令行 + parse_cmd() + + # 初始化db + init_db() + + # 启动sso定时器 秒 + Timer(SSO_TIMER_PERIOD, sso_timeout).start() + + # 启动HTTP服务 + app.run( + host=gHost, + port=gPort, + debug=gDebug + ) + + +# 入口 +if __name__ == '__main__': + main() diff --git a/server/manager_user.sh b/server/manager_user.sh new file mode 100644 index 0000000..77dc4ea --- /dev/null +++ b/server/manager_user.sh @@ -0,0 +1,152 @@ +#!/bin/bash +# ------------------------------------- +# 用户管理 +# +# @author golanstone +# @date 2020-03-25 +# ------------------------------------- + +cmdOp=$1 # 操作码 add del sudo unsudo +cmdName=$2 # 用户名 +cmdPassword=$3 # 密码 +cmdAuthorizedKeys=`echo ${@:4}` # ssh公钥 + +# cmdAuthorizedKeys="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiLQnYvqNnDcsR6lvrUL3SgmyPJ3XqGork2IxZMPyu+68dZC2/DIaVYm2G0NeEdnDlExkmIrhzRWpfmz6H748TFFsTvGxeOOR+djjKWwOMwmxU0y8QDseZqEAuCANTjzBjGu7/RUWQ5ysOKD8+UTdr1W+avumfFbBzFXNHSHA3JBFUFvFOWxcggBAlOBbA3fWig6a/ykepBfimEdgcyq/P7ERRsP5eLxasmf/vUV3vVE04SpkpMXniG8r9z3gP7At/TFWnvCWmmBJ9+EUK6FE7fxV4tmEni+IfkpQog+l5SpOp2XpMHp8YdIgotwdAKoOu3/bRsqeEMMNYErV+WsFF Stone@Golanstone" + +# 操作码 +OpAdd=1 # 添加用户 +OpDel=2 # 删除用户 +OpSudo=3 # 给用户加sudo权限 + +SUDOFILE=/etc/sudoers # 系统sudoer文件 +SYSPASSWD=/etc/passwd # 系统passwd文件 +USER_HOME_DIR=/home/$cmdName # 家目录 +USER_AUTHORIZED_KEYS=/home/$cmdName/.ssh/authorized_keys # ssh免密认证文件 +USER_SSH_DIR=/home/$cmdName/.ssh # ssh目录 +USER_SSH_RSA_PRIVATE=/home/$cmdName/.ssh/id_rsa # ssh私钥 + +# 检查cmd参数 +check() { + echo $cmdOp $cmdName $cmdPassword $cmdAuthorizedKeys +} + +# 添加用户 +add_user() { + # 判断用户是否存在 + grep "^$cmdName" $SYSPASSWD >& /dev/null + if [ $? -ne 0 ]; then + # 创建用户并设置密码 + useradd $cmdName + + echo "password" $cmdPassword + echo $cmdPassword| passwd $cmdName --stdin &>/dev/null + if [ $? -eq 0 ];then + echo "${cmdName}'s password is set successful" + else + echo "${cmdName}'s password is set failed" + fi + fi + + # 生成.ssh目录 + if [ ! -d $USER_SSH_DIR ]; then + sudo -u $cmdName mkdir $USER_SSH_DIR && chmod 700 $USER_SSH_DIR + echo "mkdir $USER_SSH_DIR" + fi + + # 生成authorized_keys文件 + if [ ! -f $USER_AUTHORIZED_KEYS ]; then + sudo -u $cmdName touch $USER_AUTHORIZED_KEYS && chmod 600 $USER_AUTHORIZED_KEYS + echo "mkdir $USER_AUTHORIZED_KEYS" + fi + + # 生成ssh秘钥对 + if [ ! -f $USER_SSH_RSA_PRIVATE ]; then + sudo -u $cmdName ssh-keygen -t rsa -f $USER_SSH_RSA_PRIVATE -P "" -q + fi + + # 添加公钥 + if [ "$cmdAuthorizedKeys" ]; then + echo "usshkey:" $cmdAuthorizedKeys + echo $cmdAuthorizedKeys >> $USER_AUTHORIZED_KEYS + fi +} + +# 删除用户 +del_user() { + grep "^$cmdName" $SYSPASSWD >& /dev/null + if [ $? -ne 0 ]; then + echo "${cmdName} is not exits" + exit 1 + fi + + # + unsudo_user + + userdel $cmdName && rm -r $USER_HOME_DIR + if [ $? -eq 0 ];then + echo "${cmdName} is delete successful" + else + echo "${cmdName} is delete failed" + fi +} + +# 添加sudo权限 +sudo_user() { + grep "^$cmdName:" $SYSPASSWD >& /dev/null + if [ $? -eq 0 ]; then + echo "$cmdName ALL=(ALL) ALL" >> ${SUDOFILE} && echo "User $cmdName add sudo success!" + else + echo "User $cmdName not exists!" + fi +} + +# 取消sudo权限 +unsudo_user() { + grep "${cmdName}" ${SUDOFILE} > /dev/null + if [ $? -eq 0 ]; then + sudo sed -i "/${cmdName}/d" ${SUDOFILE} && echo "User $1 on ${HOST} delete sudo success!" + else + echo "User $cmdName already delete sudo!" + fi +} + +check + +# 处理 +case $cmdOp in +add) + if [ -z $cmdName ] || [ -z $cmdPassword ]; then + echo "add_user invalid params" + exit 1 + fi + + add_user + ;; +del) + if [ -z $cmdName ]; then + echo "del_user invalid params" + exit 1 + fi + + del_user + ;; +sudo) + if [ -z $cmdName ]; then + echo "sudo_user invalid params" + exit 1 + fi + + sudo_user + ;; +unsudo) + if [ -z $cmdName ]; then + echo "unsudo_user invalid params" + exit 1 + fi + + unsudo_user + ;; +*) + exit 1 + ;; +esac diff --git a/server/proto.md b/server/proto.md new file mode 100644 index 0000000..34e802c --- /dev/null +++ b/server/proto.md @@ -0,0 +1,169 @@ +# 跳板机web管理协议 + + + +#### 登录 post@/login + +Param: + + - username + - password + +Resp: + +``` + {msg: "", code: 200, user: "", sessionkey:""} +``` + + + +#### 用户列表 get/post @ /jump/users + +Resp: + +```json +[[1, "cinder", 0, 0, "fuck you", "2020-04-01 15:00:22"], [2, "chenguang", 0, 0, "fuck you", "2020-04-01 15:28:17"]] +``` + + + +#### 添加用户 get/post @ /jump/user/add + +Param: + +- name +- desc + +Resp: + +​ string message + + + +#### 删除用户 get/post @ /jump/user/del + +Param: + +* name + +Resp: + +​ string message + + + +#### 修改用户 get/post @ /jump/user/modify + +Param: + +- username +- sudo +- desc + +Resp: + +​ string message + + + +#### 主机列表 get/post @ /jump/hosts + +Resp: + +```json +[[1, "ckqas129", "192.168.1.129", 22, 0, "php develop server", "2020-04-01 15:00:29"]] +``` + + + +#### 添加主机 get/post @ /jump/host/add + +Param: + +- name +- host +- port +- desc + +Resp: + +​ string message + + + +#### 删除主机 get/post @ /jump/host/del + +Param: + +- name +- host + +Resp: + +​ string message + + + +#### 添加主机用户 get/post @ /jump/host/adduser + +Param: + +- hostname +- username + +Resp: + +​ string message + + + +#### 删除主机用户 get/post @ /jump/host/deluser + +Param: + +- hostname +- username + +Resp: + +​ string message + + + +#### 修改主机用户 get/post @ /jump/host/modifyuser + +Param: + +- hostname +- username +- sudo +- desc + +Resp: + +​ string message + + + +#### 获取主机上的用户列表 get/post @ /jump/host/users + +Param: + +- hostname + +Resp: + +```json +[{"id": 1, "username": "daniel", "sudo": 1, "ctime": "2020-04-01 15:00:29"}, {"id": 2, "username": "cinder", "sudo": 0, "ctime": "2020-04-01 15:00:37"}] +``` + + + +#### 获取所有主机上的用户列表 get/post @ /jump/hostuser + +Resp: + +```json +[{"id": 1, "hostname": "ckqas129", "username": "daniel", "sudo": 1, "ctime": "2020-04-01 15:00:29"}, {"id": 2, "hostname": "ckqas129", "username": "cinder", "sudo": 0, "ctime": "2020-04-01 15:00:37"}] +``` + diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..055fd1e --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,3 @@ +requests==2.23.0 +Flask==1.1.1 +flask_cors==3.0.8