Skip to content

Commit

Permalink
- 修复mysql模式下「资产授权列表」「用户授权列表」「用户组授权列表」无法使用的问题 fixed #315
Browse files Browse the repository at this point in the history
- 修复资产新增、修改无权限的缺陷 fixed #314
- 修复执行动态指令时多行失败且无法自动执行的问题 fixed #313 #310
- 修复计划任务无法选择资产的问题 fixed #312
- 修复导入导出备份无效的问题 fixed #303
- 增加「资产详情」「资产授权」「用户详情」「用户授权」「用户组详情」「用户组授权」「角色详情」「授权策略详情」按钮
- 修复资产列表使用IP搜索无效的问题
- 资产列表增加最近接入时间排序、增加修改每页数量 fixed #311
- 修复登录页面双因素认证输入框无法自动获取焦点的问题 fixed #311
- 增加普通页面资产列表最后接入时间排序 fixed #311
- 计划任务增加执行本机系统命令
  • Loading branch information
dushixiang committed Nov 20, 2022
1 parent 4979716 commit ded4dc4
Show file tree
Hide file tree
Showing 26 changed files with 420 additions and 111 deletions.
1 change: 1 addition & 0 deletions server/common/nt/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
JobStatusNotRunning = "not-running" // 计划任务未运行状态
FuncCheckAssetStatusJob = "check-asset-status-job" // 检测资产是否在线
FuncShellJob = "shell-job" // 执行Shell脚本
JobModeSelf = "self" // 本机
JobModeAll = "all" // 全部资产
JobModeCustom = "custom" // 自定义选择资产

Expand Down
26 changes: 14 additions & 12 deletions server/model/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,27 @@ type Asset struct {
Active bool `json:"active"`
ActiveMessage string `gorm:"type:varchar(200)" json:"activeMessage"`
Created common.JsonTime `json:"created"`
LastAccessTime common.JsonTime `json:"lastAccessTime"`
Tags string `json:"tags"`
Owner string `gorm:"index,type:varchar(36)" json:"owner"`
Encrypted bool `json:"encrypted"`
AccessGatewayId string `gorm:"type:varchar(36)" json:"accessGatewayId"`
}

type AssetForPage struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
IP string `json:"ip"`
Protocol string `json:"protocol"`
Port int `json:"port"`
Active bool `json:"active"`
ActiveMessage string `json:"activeMessage"`
Created common.JsonTime `json:"created"`
Tags string `json:"tags"`
Owner string `json:"owner"`
OwnerName string `json:"ownerName"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
IP string `json:"ip"`
Protocol string `json:"protocol"`
Port int `json:"port"`
Active bool `json:"active"`
ActiveMessage string `json:"activeMessage"`
Created common.JsonTime `json:"created"`
LastAccessTime common.JsonTime `json:"lastAccessTime"`
Tags string `json:"tags"`
Owner string `json:"owner"`
OwnerName string `json:"ownerName"`
}

func (r *Asset) TableName() string {
Expand Down
16 changes: 12 additions & 4 deletions server/repository/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"
"strings"

"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/config"
Expand Down Expand Up @@ -44,7 +45,7 @@ func (r assetRepository) FindByProtocolAndIds(c context.Context, protocol string
}

func (r assetRepository) Find(c context.Context, pageIndex, pageSize int, name, protocol, tags, ip, port, active, order, field string) (o []model.AssetForPage, total int64, err error) {
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.active_message,assets.owner,assets.created,assets.tags,assets.description, users.nickname as owner_name").Joins("left join users on assets.owner = users.id")
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.active_message,assets.owner,assets.created,assets.last_access_time,assets.tags,assets.description, users.nickname as owner_name").Joins("left join users on assets.owner = users.id")
dbCounter := r.GetDB(c).Table("assets")

if len(name) > 0 {
Expand Down Expand Up @@ -104,7 +105,8 @@ func (r assetRepository) Find(c context.Context, pageIndex, pageSize int, name,
case "protocol":
case "ip":
case "active":

case "lastAccessTime":
field = "last_access_time"
default:
field = "created"
}
Expand Down Expand Up @@ -285,7 +287,7 @@ func (r assetRepository) ExistById(c context.Context, id string) (bool, error) {
}

func (r assetRepository) FindMyAssets(c context.Context, pageIndex, pageSize int, name, protocol, tags string, assetIds []string, order, field string) (o []model.AssetForPage, total int64, err error) {
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.protocol,assets.active,assets.active_message,assets.tags,assets.description").
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.protocol,assets.active,assets.active_message,assets.tags,assets.description,assets.last_access_time,").
Where("id in ?", assetIds)
dbCounter := r.GetDB(c).Table("assets").Where("id in ?", assetIds)

Expand Down Expand Up @@ -328,7 +330,8 @@ func (r assetRepository) FindMyAssets(c context.Context, pageIndex, pageSize int
case "protocol":
case "ip":
case "active":

case "lastAccessTime":
field = "last_access_time"
default:
field = "created"
}
Expand Down Expand Up @@ -362,3 +365,8 @@ func (r assetRepository) FindMyAssetTags(c context.Context, assetIds []string) (

return utils.Distinct(o), nil
}

func (r assetRepository) UpdateLastAccessTime(ctx context.Context, assetId string, now common.JsonTime) error {
asset := &model.Asset{ID: assetId, LastAccessTime: now}
return r.GetDB(ctx).Table("assets").Updates(asset).Error
}
9 changes: 3 additions & 6 deletions server/repository/authorised.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ func (r authorisedRepository) FindAssetPage(c context.Context, pageIndex, pageSi
db := r.GetDB(c).Table("assets").
Select("authorised.id, authorised.created, assets.id as asset_id, assets.name as asset_name, strategies.id as strategy_id, strategies.name as strategy_name ").
Joins("left join authorised on authorised.asset_id = assets.id").
Joins("left join strategies on strategies.id = authorised.strategy_id").
Group("assets.id")
Joins("left join strategies on strategies.id = authorised.strategy_id")
dbCounter := r.GetDB(c).Table("assets").Joins("left join authorised on assets.id = authorised.asset_id").Group("assets.id")

if assetName != "" {
Expand Down Expand Up @@ -110,8 +109,7 @@ func (r authorisedRepository) FindUserPage(c context.Context, pageIndex, pageSiz
db := r.GetDB(c).Table("users").
Select("authorised.id, authorised.created, users.id as user_id, users.nickname as user_name, strategies.id as strategy_id, strategies.name as strategy_name ").
Joins("left join authorised on authorised.user_id = users.id").
Joins("left join strategies on strategies.id = authorised.strategy_id").
Group("users.id")
Joins("left join strategies on strategies.id = authorised.strategy_id")
dbCounter := r.GetDB(c).Table("assets").Joins("left join authorised on assets.id = authorised.asset_id").Group("assets.id")

if userName != "" {
Expand Down Expand Up @@ -140,8 +138,7 @@ func (r authorisedRepository) FindUserGroupPage(c context.Context, pageIndex, pa
db := r.GetDB(c).Table("user_groups").
Select("authorised.id, authorised.created, user_groups.id as user_group_id, user_groups.name as user_group_name, strategies.id as strategy_id, strategies.name as strategy_name ").
Joins("left join authorised on authorised.user_group_id = user_groups.id").
Joins("left join strategies on strategies.id = authorised.strategy_id").
Group("user_groups.id")
Joins("left join strategies on strategies.id = authorised.strategy_id")
dbCounter := r.GetDB(c).Table("assets").Joins("left join authorised on assets.id = authorised.asset_id").Group("assets.id")

if userName != "" {
Expand Down
7 changes: 6 additions & 1 deletion server/service/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ func getJob(j *model.Job) (job cron.Job, err error) {
Metadata: j.Metadata,
}
case nt.FuncShellJob:
job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata}
job = ShellJob{
ID: j.ID,
Mode: j.Mode,
ResourceIds: j.ResourceIds,
Metadata: j.Metadata,
}
default:
return nil, errors.New("未识别的任务")
}
Expand Down
55 changes: 45 additions & 10 deletions server/service/job_exec_shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"encoding/json"
"errors"
"fmt"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/common/term"
"strings"
"time"

"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/common/term"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
Expand All @@ -35,13 +35,19 @@ func (r ShellJob) Run() {
return
}

var assets []model.Asset
if r.Mode == nt.JobModeAll {
assets, _ = repository.AssetRepository.FindByProtocol(context.TODO(), "ssh")
} else {
assets, _ = repository.AssetRepository.FindByProtocolAndIds(context.TODO(), "ssh", strings.Split(r.ResourceIds, ","))
switch r.Mode {
case nt.JobModeAll:
assets, _ := repository.AssetRepository.FindByProtocol(context.TODO(), "ssh")
r.executeShellByAssets(assets)
case nt.JobModeCustom:
assets, _ := repository.AssetRepository.FindByProtocolAndIds(context.TODO(), "ssh", strings.Split(r.ResourceIds, ","))
r.executeShellByAssets(assets)
case nt.JobModeSelf:
r.executeShellByLocal()
}
}

func (r ShellJob) executeShellByAssets(assets []model.Asset) {
if len(assets) == 0 {
return
}
Expand Down Expand Up @@ -89,7 +95,7 @@ func (r ShellJob) Run() {

go func() {
t1 := time.Now()
result, err := exec(metadataShell.Shell, asset.AccessGatewayId, ip, port, username, password, privateKey, passphrase)
result, err := execute(metadataShell.Shell, asset.AccessGatewayId, ip, port, username, password, privateKey, passphrase)
elapsed := time.Since(t1)
var msg string
if err != nil {
Expand Down Expand Up @@ -124,7 +130,36 @@ func (r ShellJob) Run() {
_ = repository.JobLogRepository.Create(context.TODO(), &jobLog)
}

func exec(shell, accessGatewayId, ip string, port int, username, password, privateKey, passphrase string) (string, error) {
func (r ShellJob) executeShellByLocal() {
var metadataShell MetadataShell
err := json.Unmarshal([]byte(r.Metadata), &metadataShell)
if err != nil {
log.Error("JSON数据解析失败", log.String("err", err.Error()))
return
}

now := time.Now()
var msg = ""
log.Debug("run local command", log.String("cmd", metadataShell.Shell))
output, outerr, err := utils.Exec(metadataShell.Shell)
if err != nil {
msg = fmt.Sprintf("命令执行失败,错误内容为:「%v」,耗时「%v」", err.Error(), time.Since(now).String())
} else {
msg = fmt.Sprintf("命令执行成功,stdout 返回值「%v」,stderr 返回值「%v」,耗时「%v」", output, outerr, time.Since(now).String())
}

_ = repository.JobRepository.UpdateLastUpdatedById(context.Background(), r.ID)
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: common.NowJsonTime(),
Message: msg,
}

_ = repository.JobLogRepository.Create(context.Background(), &jobLog)
}

func execute(shell, accessGatewayId, ip string, port int, username, password, privateKey, passphrase string) (string, error) {
if accessGatewayId != "" && accessGatewayId != "-" {
g, err := GatewayService.GetGatewayById(accessGatewayId)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions server/service/menu_default_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ var DefaultMenu = []*model.Menu{
),
model.NewMenu("asset-add", "新建", "asset",
model.NewPermission("POST", "/assets"),
model.NewPermission("GET", "/access-gateways"),
),
model.NewMenu("asset-edit", "编辑", "asset",
model.NewPermission("GET", "/assets/:id"),
model.NewPermission("PUT", "/assets/:id"),
model.NewPermission("GET", "/access-gateways"),
),
model.NewMenu("asset-del", "删除", "asset",
model.NewPermission("DELETE", "/assets/:id"),
Expand Down Expand Up @@ -191,6 +193,10 @@ var DefaultMenu = []*model.Menu{
model.NewPermission("POST", "/storage-logs/clear"),
),

model.NewMenu("session-command", "命令日志", "log-audit",
model.NewPermission("GET", "/session-commands/paging"),
),

model.NewMenu("ops", "系统运维", "root"),

model.NewMenu("job", "计划任务", "ops",
Expand Down
3 changes: 3 additions & 0 deletions server/service/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ func (service sessionService) Create(clientIp, assetId, mode string, user *model
if err := repository.SessionRepository.Create(context.TODO(), s); err != nil {
return nil, err
}
if err := repository.AssetRepository.UpdateLastAccessTime(context.Background(), s.AssetId, common.NowJsonTime()); err != nil {
return nil, err
}
return s, nil
}

Expand Down
18 changes: 18 additions & 0 deletions server/utils/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package utils

import (
"bytes"
"os/exec"
)

// Exec 执行shell命令
func Exec(command string) (string, string, error) {
var stdout bytes.Buffer
var stderr bytes.Buffer

cmd := exec.Command("bash", "-c", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return stdout.String(), stderr.String(), err
}
22 changes: 22 additions & 0 deletions server/utils/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package utils

import "testing"

func TestExec(t *testing.T) {
commands := []string{
`pwd`,
`whoami`,
`cat /etc/resolv.conf`,
`echo "test" > /tmp/ddtest`,
`rm -rf /tmp/ddtest`,
}
for _, command := range commands {
output, errout, err := Exec(command)
if err != nil {
t.Fatal(err)
}
t.Log("output:", output)
t.Log("errout:", errout)
}

}
35 changes: 20 additions & 15 deletions web/src/components/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import React, {useEffect, useState} from 'react';
import {Button, Card, Checkbox, Form, Input, message, Modal, Typography} from "antd";
import './Login.css'
import request from "../common/request";
import {LockOutlined, LockTwoTone, UserOutlined} from '@ant-design/icons';
import {LockOutlined, UserOutlined} from '@ant-design/icons';
import {setToken} from "../utils/utils";
import brandingApi from "../api/branding";
import strings from "../utils/strings";
import {useNavigate} from "react-router-dom";
import {setCurrentUser} from "../service/permission";
import PromptModal from "../dd/prompt-modal/prompt-modal";

const {Title} = Typography;
const {Title, Text} = Typography;

const LoginForm = () => {

const navigate = useNavigate();

let [inLogin, setInLogin] = useState(false);
let [branding, setBranding] = useState({});
let [prompt, setPrompt] = useState(false);
let [account, setAccount] = useState({});

useEffect(() => {
const x = async () => {
Expand Down Expand Up @@ -62,25 +65,15 @@ const LoginForm = () => {
return false;
}

const showTOTP = (loginAccount) => {
let value = '';
Modal.confirm({
title: '双因素认证',
icon: <LockTwoTone/>,
content: <Input onChange={e => value = e.target.value} onPressEnter={() => handleOk(loginAccount, value)}
placeholder="请输入双因素认证码"/>,
onOk: () => handleOk(loginAccount, value),
});
}

const handleSubmit = async params => {
setInLogin(true);

try {
let result = await request.post('/login', params);
if (result.code === 100) {
// 进行双因素认证
showTOTP(params);
setPrompt(true);
setAccount(params);
return;
}
if (result.code !== 1) {
Expand All @@ -100,7 +93,7 @@ const LoginForm = () => {
<Card className='login-card' title={null}>
<div style={{textAlign: "center", margin: '15px auto 30px auto', color: '#1890ff'}}>
<Title level={1}>{branding['name']}</Title>
{/*<Text>一个轻量级的堡垒机系统</Text>*/}
<Text>{branding['description']}</Text>
</div>
<Form onFinish={handleSubmit} className="login-form">
<Form.Item name='username' rules={[{required: true, message: '请输入登录账号!'}]}>
Expand All @@ -120,6 +113,18 @@ const LoginForm = () => {
</Form.Item>
</Form>
</Card>

<PromptModal
title={'双因素认证'}
open={prompt}
onOk={(value) => {
handleOk(account, value)
}}
onCancel={() => setPrompt(false)}
placeholder={"请输入双因素认证码"}
>

</PromptModal>
</div>

);
Expand Down
Loading

0 comments on commit ded4dc4

Please sign in to comment.