mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
parent
4d279a521e
commit
bcd88c6eca
@ -131,28 +131,6 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, data)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags SSH
|
|
||||||
// @Summary Analysis host SSH logs
|
|
||||||
// @Description 分析 SSH 登录日志
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.SearchForAnalysis true "request"
|
|
||||||
// @Success 200 {array} dto.SSHLogAnalysis
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /host/ssh/log/analysis [post]
|
|
||||||
func (b *BaseApi) AnalysisLog(c *gin.Context) {
|
|
||||||
var req dto.SearchForAnalysis
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := sshService.AnalysisLog(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags SSH
|
// @Tags SSH
|
||||||
// @Summary Load host SSH logs
|
// @Summary Load host SSH logs
|
||||||
// @Description 获取 SSH 登录日志
|
// @Description 获取 SSH 登录日志
|
||||||
|
@ -44,26 +44,6 @@ type SSHLog struct {
|
|||||||
FailedCount int `json:"failedCount"`
|
FailedCount int `json:"failedCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchForAnalysis struct {
|
|
||||||
PageInfo
|
|
||||||
OrderBy string `json:"orderBy" validate:"required,oneof=Success Failed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnalysisRes struct {
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
Items []SSHLogAnalysis `json:"items"`
|
|
||||||
SuccessfulCount int `json:"successfulCount"`
|
|
||||||
FailedCount int `json:"failedCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHLogAnalysis struct {
|
|
||||||
Address string `json:"address"`
|
|
||||||
Area string `json:"area"`
|
|
||||||
SuccessfulCount int `json:"successfulCount"`
|
|
||||||
FailedCount int `json:"failedCount"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHHistory struct {
|
type SSHHistory struct {
|
||||||
Date time.Time `json:"date"`
|
Date time.Time `json:"date"`
|
||||||
DateStr string `json:"dateStr"`
|
DateStr string `json:"dateStr"`
|
||||||
|
@ -32,7 +32,6 @@ type ISSHService interface {
|
|||||||
UpdateByFile(value string) error
|
UpdateByFile(value string) error
|
||||||
Update(req dto.SSHUpdate) error
|
Update(req dto.SSHUpdate) error
|
||||||
GenerateSSH(req dto.GenerateSSH) error
|
GenerateSSH(req dto.GenerateSSH) error
|
||||||
AnalysisLog(req dto.SearchForAnalysis) (*dto.AnalysisRes, error)
|
|
||||||
LoadSSHSecret(mode string) (string, error)
|
LoadSSHSecret(mode string) (string, error)
|
||||||
LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error)
|
LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error)
|
||||||
|
|
||||||
@ -310,9 +309,9 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
|||||||
case constant.StatusSuccess:
|
case constant.StatusSuccess:
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||||
case constant.StatusFailed:
|
case constant.StatusFailed:
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", file.Name, command)
|
commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' %s", file.Name, command)
|
||||||
default:
|
default:
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file.Name, command)
|
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
|
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
|
||||||
@ -320,9 +319,9 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
|||||||
case constant.StatusSuccess:
|
case constant.StatusSuccess:
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||||
case constant.StatusFailed:
|
case constant.StatusFailed:
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' | grep -v 'invalid' %s", file.Name, command)
|
commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' %s", file.Name, command)
|
||||||
default:
|
default:
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file.Name, command)
|
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" %s", file.Name, command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataItem, successCount, failedCount := loadSSHData(commandItem, showCountFrom, showCountTo, file.Year, qqWry, nyc)
|
dataItem, successCount, failedCount := loadSSHData(commandItem, showCountFrom, showCountTo, file.Year, qqWry, nyc)
|
||||||
@ -337,89 +336,6 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
|||||||
return &data, nil
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SSHService) AnalysisLog(req dto.SearchForAnalysis) (*dto.AnalysisRes, error) {
|
|
||||||
var fileList []string
|
|
||||||
baseDir := "/var/log"
|
|
||||||
if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) {
|
|
||||||
if !strings.HasSuffix(info.Name(), ".gz") {
|
|
||||||
fileList = append(fileList, pathItem)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
itemFileName := strings.TrimSuffix(pathItem, ".gz")
|
|
||||||
if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) {
|
|
||||||
if err := handleGunzip(pathItem); err == nil {
|
|
||||||
fileList = append(fileList, itemFileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
command := ""
|
|
||||||
sortMap := make(map[string]dto.SSHLogAnalysis)
|
|
||||||
for _, file := range fileList {
|
|
||||||
commandItem := ""
|
|
||||||
if strings.HasPrefix(path.Base(file), "secure") {
|
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file, command)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(path.Base(file), "auth.log") {
|
|
||||||
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file, command)
|
|
||||||
}
|
|
||||||
loadSSHDataForAnalysis(sortMap, commandItem)
|
|
||||||
}
|
|
||||||
var sortSlice []dto.SSHLogAnalysis
|
|
||||||
for key, value := range sortMap {
|
|
||||||
sortSlice = append(sortSlice, dto.SSHLogAnalysis{Address: key, SuccessfulCount: value.SuccessfulCount, FailedCount: value.FailedCount, Status: "accept"})
|
|
||||||
}
|
|
||||||
if req.OrderBy == constant.StatusSuccess {
|
|
||||||
sort.Slice(sortSlice, func(i, j int) bool {
|
|
||||||
return sortSlice[i].SuccessfulCount > sortSlice[j].SuccessfulCount
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
sort.Slice(sortSlice, func(i, j int) bool {
|
|
||||||
return sortSlice[i].FailedCount > sortSlice[j].FailedCount
|
|
||||||
})
|
|
||||||
}
|
|
||||||
qqWry, _ := qqwry.NewQQwry()
|
|
||||||
rules, _ := listIpRules("drop")
|
|
||||||
for i := 0; i < len(sortSlice); i++ {
|
|
||||||
sortSlice[i].Area = qqWry.Find(sortSlice[i].Address).Area
|
|
||||||
for _, rule := range rules {
|
|
||||||
if sortSlice[i].Address == rule {
|
|
||||||
sortSlice[i].Status = "drop"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var backData dto.AnalysisRes
|
|
||||||
for _, item := range sortSlice {
|
|
||||||
backData.FailedCount += item.FailedCount
|
|
||||||
backData.SuccessfulCount += item.SuccessfulCount
|
|
||||||
}
|
|
||||||
|
|
||||||
var data []dto.SSHLogAnalysis
|
|
||||||
total, start, end := len(sortSlice), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
|
||||||
if start > total {
|
|
||||||
data = make([]dto.SSHLogAnalysis, 0)
|
|
||||||
} else {
|
|
||||||
if end >= total {
|
|
||||||
end = total
|
|
||||||
}
|
|
||||||
data = sortSlice[start:end]
|
|
||||||
}
|
|
||||||
backData.Items = data
|
|
||||||
backData.Total = int64(total)
|
|
||||||
|
|
||||||
return &backData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *SSHService) LoadSSHConf() (string, error) {
|
func (u *SSHService) LoadSSHConf() (string, error) {
|
||||||
if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil {
|
if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil {
|
||||||
return "", buserr.New("ErrHttpReqNotFound")
|
return "", buserr.New("ErrHttpReqNotFound")
|
||||||
@ -529,147 +445,62 @@ func loadSSHData(command string, showCountFrom, showCountTo, currentYear int, qq
|
|||||||
return datas, successCount, failedCount
|
return datas, successCount, failedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSSHDataForAnalysis(analysisMap map[string]dto.SSHLogAnalysis, commandItem string) {
|
|
||||||
stdout, err := cmd.Exec(commandItem)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(stdout), "\n")
|
|
||||||
for i := len(lines) - 1; i >= 0; i-- {
|
|
||||||
var itemData dto.SSHHistory
|
|
||||||
switch {
|
|
||||||
case strings.Contains(lines[i], "Failed password for"):
|
|
||||||
itemData = loadFailedSecureDatas(lines[i])
|
|
||||||
case strings.Contains(lines[i], "Connection closed by authenticating user"):
|
|
||||||
itemData = loadFailedAuthDatas(lines[i])
|
|
||||||
case strings.Contains(lines[i], "Accepted "):
|
|
||||||
itemData = loadSuccessDatas(lines[i])
|
|
||||||
}
|
|
||||||
if len(itemData.Address) != 0 {
|
|
||||||
if val, ok := analysisMap[itemData.Address]; ok {
|
|
||||||
if itemData.Status == constant.StatusSuccess {
|
|
||||||
val.SuccessfulCount++
|
|
||||||
} else {
|
|
||||||
val.FailedCount++
|
|
||||||
}
|
|
||||||
analysisMap[itemData.Address] = val
|
|
||||||
} else {
|
|
||||||
item := dto.SSHLogAnalysis{
|
|
||||||
Address: itemData.Address,
|
|
||||||
SuccessfulCount: 0,
|
|
||||||
FailedCount: 0,
|
|
||||||
}
|
|
||||||
if itemData.Status == constant.StatusSuccess {
|
|
||||||
item.SuccessfulCount = 1
|
|
||||||
} else {
|
|
||||||
item.FailedCount = 1
|
|
||||||
}
|
|
||||||
analysisMap[itemData.Address] = item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSuccessDatas(line string) dto.SSHHistory {
|
func loadSuccessDatas(line string) dto.SSHHistory {
|
||||||
var data dto.SSHHistory
|
var data dto.SSHHistory
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
index, dataStr := analyzeDateStr(parts)
|
||||||
if err != nil {
|
if dataStr == "" {
|
||||||
if len(parts) < 14 {
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
data = dto.SSHHistory{
|
data.DateStr = dataStr
|
||||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
data.AuthMode = parts[4+index]
|
||||||
AuthMode: parts[6],
|
data.User = parts[6+index]
|
||||||
User: parts[8],
|
data.Address = parts[8+index]
|
||||||
Address: parts[10],
|
data.Port = parts[10+index]
|
||||||
Port: parts[12],
|
data.Status = constant.StatusSuccess
|
||||||
Status: constant.StatusSuccess,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(parts) < 12 {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
data = dto.SSHHistory{
|
|
||||||
DateStr: t.Format("2006 Jan 2 15:04:05"),
|
|
||||||
AuthMode: parts[4],
|
|
||||||
User: parts[6],
|
|
||||||
Address: parts[8],
|
|
||||||
Port: parts[10],
|
|
||||||
Status: constant.StatusSuccess,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
||||||
var data dto.SSHHistory
|
var data dto.SSHHistory
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
index, dataStr := analyzeDateStr(parts)
|
||||||
if err != nil {
|
if dataStr == "" {
|
||||||
if len(parts) < 14 {
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
data = dto.SSHHistory{
|
data.DateStr = dataStr
|
||||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
if index == 2 {
|
||||||
AuthMode: parts[8],
|
data.User = parts[10]
|
||||||
User: parts[10],
|
|
||||||
Address: parts[11],
|
|
||||||
Port: parts[13],
|
|
||||||
Status: constant.StatusFailed,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if len(parts) < 12 {
|
data.User = parts[7]
|
||||||
return data
|
|
||||||
}
|
|
||||||
data = dto.SSHHistory{
|
|
||||||
DateStr: t.Format("2006 Jan 2 15:04:05"),
|
|
||||||
AuthMode: parts[6],
|
|
||||||
User: parts[7],
|
|
||||||
Address: parts[9],
|
|
||||||
Port: parts[11],
|
|
||||||
Status: constant.StatusFailed,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
data.AuthMode = parts[6+index]
|
||||||
|
data.Address = parts[9+index]
|
||||||
|
data.Port = parts[11+index]
|
||||||
|
data.Status = constant.StatusFailed
|
||||||
if strings.Contains(line, ": ") {
|
if strings.Contains(line, ": ") {
|
||||||
data.Message = strings.Split(line, ": ")[1]
|
data.Message = strings.Split(line, ": ")[1]
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFailedSecureDatas(line string) dto.SSHHistory {
|
func loadFailedSecureDatas(line string) dto.SSHHistory {
|
||||||
var data dto.SSHHistory
|
var data dto.SSHHistory
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
index, dataStr := analyzeDateStr(parts)
|
||||||
if err != nil {
|
if dataStr == "" {
|
||||||
if len(parts) < 14 {
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
data = dto.SSHHistory{
|
data.DateStr = dataStr
|
||||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
if strings.Contains(line, " invalid ") {
|
||||||
AuthMode: parts[6],
|
data.AuthMode = parts[4+index]
|
||||||
User: parts[8],
|
index += 2
|
||||||
Address: parts[10],
|
|
||||||
Port: parts[12],
|
|
||||||
Status: constant.StatusFailed,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if len(parts) < 12 {
|
data.AuthMode = parts[4+index]
|
||||||
return data
|
|
||||||
}
|
|
||||||
index := 0
|
|
||||||
if strings.Contains(line, " invalid user") {
|
|
||||||
index = 2
|
|
||||||
}
|
|
||||||
data = dto.SSHHistory{
|
|
||||||
DateStr: t.Format("2006 Jan 2 15:04:05"),
|
|
||||||
AuthMode: parts[4],
|
|
||||||
User: parts[index+6],
|
|
||||||
Address: parts[index+8],
|
|
||||||
Port: parts[index+10],
|
|
||||||
Status: constant.StatusFailed,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
data.User = parts[6+index]
|
||||||
|
data.Address = parts[8+index]
|
||||||
|
data.Port = parts[10+index]
|
||||||
|
data.Status = constant.StatusFailed
|
||||||
if strings.Contains(line, ": ") {
|
if strings.Contains(line, ": ") {
|
||||||
data.Message = strings.Split(line, ": ")[1]
|
data.Message = strings.Split(line, ": ")[1]
|
||||||
}
|
}
|
||||||
@ -699,3 +530,17 @@ func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
|
|||||||
}
|
}
|
||||||
return itemDate
|
return itemDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func analyzeDateStr(parts []string) (int, string) {
|
||||||
|
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
||||||
|
if err != nil {
|
||||||
|
if len(parts) < 14 {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
|
||||||
|
}
|
||||||
|
if len(parts) < 12 {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
return 0, t.Format("2006 Jan 2 15:04:05")
|
||||||
|
}
|
||||||
|
@ -41,7 +41,6 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
|
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
|
||||||
hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret)
|
hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret)
|
||||||
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
||||||
hostRouter.POST("/ssh/log/analysis", baseApi.AnalysisLog)
|
|
||||||
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
|
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
|
||||||
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
||||||
|
|
||||||
|
@ -6582,45 +6582,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/host/ssh/log/analysis": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "分析 SSH 登录日志",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"SSH"
|
|
||||||
],
|
|
||||||
"summary": "Analysis host SSH logs",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "request",
|
|
||||||
"name": "request",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/dto.SearchForAnalysis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/dto.SSHLogAnalysis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/ssh/operate": {
|
"/host/ssh/operate": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -16568,26 +16529,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dto.SSHLogAnalysis": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"address": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"failedCount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"successfulCount": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dto.SSHUpdate": {
|
"dto.SSHUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -16642,29 +16583,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dto.SearchForAnalysis": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"orderBy",
|
|
||||||
"page",
|
|
||||||
"pageSize"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"orderBy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Success",
|
|
||||||
"Failed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"page": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"pageSize": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dto.SearchForTree": {
|
"dto.SearchForTree": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -6575,45 +6575,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/host/ssh/log/analysis": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "分析 SSH 登录日志",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"SSH"
|
|
||||||
],
|
|
||||||
"summary": "Analysis host SSH logs",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "request",
|
|
||||||
"name": "request",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/dto.SearchForAnalysis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/dto.SSHLogAnalysis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/ssh/operate": {
|
"/host/ssh/operate": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -16561,26 +16522,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dto.SSHLogAnalysis": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"address": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"failedCount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"successfulCount": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dto.SSHUpdate": {
|
"dto.SSHUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -16635,29 +16576,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dto.SearchForAnalysis": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"orderBy",
|
|
||||||
"page",
|
|
||||||
"pageSize"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"orderBy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Success",
|
|
||||||
"Failed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"page": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"pageSize": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dto.SearchForTree": {
|
"dto.SearchForTree": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2150,19 +2150,6 @@ definitions:
|
|||||||
totalCount:
|
totalCount:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
dto.SSHLogAnalysis:
|
|
||||||
properties:
|
|
||||||
address:
|
|
||||||
type: string
|
|
||||||
area:
|
|
||||||
type: string
|
|
||||||
failedCount:
|
|
||||||
type: integer
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
successfulCount:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
dto.SSHUpdate:
|
dto.SSHUpdate:
|
||||||
properties:
|
properties:
|
||||||
key:
|
key:
|
||||||
@ -2200,22 +2187,6 @@ definitions:
|
|||||||
- ssl
|
- ssl
|
||||||
- sslType
|
- sslType
|
||||||
type: object
|
type: object
|
||||||
dto.SearchForAnalysis:
|
|
||||||
properties:
|
|
||||||
orderBy:
|
|
||||||
enum:
|
|
||||||
- Success
|
|
||||||
- Failed
|
|
||||||
type: string
|
|
||||||
page:
|
|
||||||
type: integer
|
|
||||||
pageSize:
|
|
||||||
type: integer
|
|
||||||
required:
|
|
||||||
- orderBy
|
|
||||||
- page
|
|
||||||
- pageSize
|
|
||||||
type: object
|
|
||||||
dto.SearchForTree:
|
dto.SearchForTree:
|
||||||
properties:
|
properties:
|
||||||
info:
|
info:
|
||||||
@ -8966,30 +8937,6 @@ paths:
|
|||||||
summary: Load host SSH logs
|
summary: Load host SSH logs
|
||||||
tags:
|
tags:
|
||||||
- SSH
|
- SSH
|
||||||
/host/ssh/log/analysis:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: 分析 SSH 登录日志
|
|
||||||
parameters:
|
|
||||||
- description: request
|
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/dto.SearchForAnalysis'
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/dto.SSHLogAnalysis'
|
|
||||||
type: array
|
|
||||||
security:
|
|
||||||
- ApiKeyAuth: []
|
|
||||||
summary: Analysis host SSH logs
|
|
||||||
tags:
|
|
||||||
- SSH
|
|
||||||
/host/ssh/operate:
|
/host/ssh/operate:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -126,6 +126,3 @@ export const loadSecret = (mode: string) => {
|
|||||||
export const loadSSHLogs = (params: Host.searchSSHLog) => {
|
export const loadSSHLogs = (params: Host.searchSSHLog) => {
|
||||||
return http.post<Host.sshLog>(`/hosts/ssh/log`, params);
|
return http.post<Host.sshLog>(`/hosts/ssh/log`, params);
|
||||||
};
|
};
|
||||||
export const loadAnalysis = (params: Host.analysisSSHLog) => {
|
|
||||||
return http.post<Host.logAnalysisRes>(`/hosts/ssh/log/analysis`, params, TimeoutEnum.T_40S);
|
|
||||||
};
|
|
||||||
|
@ -1,259 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
|
||||||
<template #header>
|
|
||||||
<DrawerHeader :header="$t('menu.home')" :back="handleClose" />
|
|
||||||
</template>
|
|
||||||
<div v-loading="loading">
|
|
||||||
<el-form label-position="top" class="ml-5">
|
|
||||||
<el-row type="flex" justify="center" :gutter="20">
|
|
||||||
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
|
|
||||||
<el-form-item>
|
|
||||||
<template #label>
|
|
||||||
<span class="status-label">{{ $t('ssh.successful') }}</span>
|
|
||||||
</template>
|
|
||||||
<span class="status-count">{{ successfulTotalCount }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
|
|
||||||
<el-form-item>
|
|
||||||
<template #label>
|
|
||||||
<span class="status-label">{{ $t('ssh.failed') }}</span>
|
|
||||||
</template>
|
|
||||||
<span class="status-count">{{ failedTotalCount }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-button type="primary" @click="onChangeStatus('accept', null)" :disabled="selects.length === 0">
|
|
||||||
{{ $t('firewall.allow') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="onChangeStatus('drop', null)" :disabled="selects.length === 0">
|
|
||||||
{{ $t('firewall.deny') }}
|
|
||||||
</el-button>
|
|
||||||
<ComplexTable v-model:selects="selects" class="mt-5" :data="data" @header-click="changeSort">
|
|
||||||
<el-table-column type="selection" fix :selectable="selectable" />
|
|
||||||
<el-table-column :label="$t('logs.loginIP')" prop="address" min-width="40" />
|
|
||||||
<el-table-column :label="$t('ssh.belong')" prop="area" min-width="40" />
|
|
||||||
<el-table-column prop="successfulCount" min-width="20">
|
|
||||||
<template #header>
|
|
||||||
{{ $t('ssh.successful') }}
|
|
||||||
<el-icon style="cursor: pointer" @click="(orderBy = 'Success') && search()">
|
|
||||||
<CaretBottom />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" link>{{ row.successfulCount }}</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="failedCount" min-width="20">
|
|
||||||
<template #header>
|
|
||||||
{{ $t('ssh.failed') }}
|
|
||||||
<el-icon style="cursor: pointer" @click="(orderBy = 'Failed') && search()">
|
|
||||||
<CaretBottom />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="danger" link>{{ row.failedCount }}</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :min-width="30" :label="$t('commons.table.status')" prop="strategy">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button
|
|
||||||
v-if="row.status === 'accept'"
|
|
||||||
:disabled="!selectable(row)"
|
|
||||||
@click="onChangeStatus('drop', row)"
|
|
||||||
link
|
|
||||||
type="success"
|
|
||||||
>
|
|
||||||
{{ $t('commons.status.accept') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-else
|
|
||||||
link
|
|
||||||
:disabled="!selectable(row)"
|
|
||||||
type="danger"
|
|
||||||
@click="onChangeStatus('accept', row)"
|
|
||||||
>
|
|
||||||
{{ $t('commons.status.deny') }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</ComplexTable>
|
|
||||||
<fu-table-pagination
|
|
||||||
class="float-right mt-4"
|
|
||||||
v-model:current-page="paginationConfig.currentPage"
|
|
||||||
v-model:page-size="paginationConfig.pageSize"
|
|
||||||
v-bind="paginationConfig"
|
|
||||||
@change="search()"
|
|
||||||
:layout="'prev, pager, next'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-drawer>
|
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
v-model="dialogVisible"
|
|
||||||
:title="$t('firewall.' + (operation === 'drop' ? 'deny' : 'allow'))"
|
|
||||||
width="30%"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="20" :offset="2">
|
|
||||||
<el-alert :title="msg" show-icon type="error" :closable="false"></el-alert>
|
|
||||||
<div class="resource">
|
|
||||||
<ul v-for="(row, index) in operationList" :key="index">
|
|
||||||
<li>
|
|
||||||
<span>{{ row }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="submitOperation" v-loading="loading">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
|
||||||
import { loadAnalysis, operateIPRule } from '@/api/modules/host';
|
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { Host } from '@/api/interface/host';
|
|
||||||
|
|
||||||
const drawerVisible = ref();
|
|
||||||
const loading = ref();
|
|
||||||
const data = ref();
|
|
||||||
const successfulTotalCount = ref();
|
|
||||||
const failedTotalCount = ref();
|
|
||||||
const selects = ref<any>([]);
|
|
||||||
const orderBy = ref<string>('Failed');
|
|
||||||
|
|
||||||
const dialogVisible = ref();
|
|
||||||
const msg = ref();
|
|
||||||
const operation = ref();
|
|
||||||
const operationList = ref();
|
|
||||||
|
|
||||||
const paginationConfig = reactive({
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const acceptParams = (): void => {
|
|
||||||
search();
|
|
||||||
drawerVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const search = async () => {
|
|
||||||
let params = {
|
|
||||||
page: paginationConfig.currentPage,
|
|
||||||
pageSize: paginationConfig.pageSize,
|
|
||||||
orderBy: orderBy.value,
|
|
||||||
};
|
|
||||||
loading.value = true;
|
|
||||||
loadAnalysis(params)
|
|
||||||
.then((res) => {
|
|
||||||
loading.value = false;
|
|
||||||
data.value = res.data.items || [];
|
|
||||||
successfulTotalCount.value = res.data.successfulCount;
|
|
||||||
failedTotalCount.value = res.data.failedCount;
|
|
||||||
paginationConfig.total = res.data.total;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function selectable(row: any): boolean {
|
|
||||||
return row.address !== '127.0.0.1' && row.address !== '::1';
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeSort = (column: any) => {
|
|
||||||
switch (column.property) {
|
|
||||||
case 'successfulCount':
|
|
||||||
orderBy.value = 'Success';
|
|
||||||
search();
|
|
||||||
return;
|
|
||||||
case 'failedCount':
|
|
||||||
orderBy.value = 'Failed';
|
|
||||||
search();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeStatus = async (status: string, row: Host.logAnalysis | null) => {
|
|
||||||
operationList.value = [];
|
|
||||||
if (row) {
|
|
||||||
if (row.status !== status) {
|
|
||||||
operationList.value.push(row.address);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const item of selects.value) {
|
|
||||||
if (item.status !== status) {
|
|
||||||
operationList.value.push(item.address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (operationList.value.length === 0) {
|
|
||||||
MsgError(
|
|
||||||
i18n.global.t('ssh.noAddrWarning', [i18n.global.t('firewall.' + (status === 'drop' ? 'deny' : 'allow'))]),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
operation.value = status;
|
|
||||||
msg.value = status === 'drop' ? i18n.global.t('ssh.denyHelper') : i18n.global.t('ssh.acceptHelper');
|
|
||||||
dialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitOperation = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
operateIPRule({
|
|
||||||
operation: operation.value === 'drop' ? 'add' : 'remove',
|
|
||||||
address: operationList.value.join(','),
|
|
||||||
strategy: 'drop',
|
|
||||||
description: '',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
dialogVisible.value = false;
|
|
||||||
search();
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
drawerVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.resource {
|
|
||||||
margin-top: 10px;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
x
Reference in New Issue
Block a user