From bcd88c6eca7795c1861c7d37ba8bf70ae472e8fe Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Fri, 15 Dec 2023 18:12:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20SSH=20=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=8E=B7=E5=8F=96=20(#3348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #3337 --- backend/app/api/v1/ssh.go | 22 -- backend/app/dto/ssh.go | 20 -- backend/app/service/ssh.go | 263 ++++-------------- backend/router/ro_host.go | 1 - cmd/server/docs/docs.go | 82 ------ cmd/server/docs/swagger.json | 82 ------ cmd/server/docs/swagger.yaml | 53 ---- frontend/src/api/modules/host.ts | 3 - .../src/views/host/ssh/log/analysis/index.vue | 259 ----------------- 9 files changed, 54 insertions(+), 731 deletions(-) delete mode 100644 frontend/src/views/host/ssh/log/analysis/index.vue diff --git a/backend/app/api/v1/ssh.go b/backend/app/api/v1/ssh.go index a7adc7930..b522a42b5 100644 --- a/backend/app/api/v1/ssh.go +++ b/backend/app/api/v1/ssh.go @@ -131,28 +131,6 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) { 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 // @Summary Load host SSH logs // @Description 获取 SSH 登录日志 diff --git a/backend/app/dto/ssh.go b/backend/app/dto/ssh.go index 0773138ed..c07a3f4e9 100644 --- a/backend/app/dto/ssh.go +++ b/backend/app/dto/ssh.go @@ -44,26 +44,6 @@ type SSHLog struct { 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 { Date time.Time `json:"date"` DateStr string `json:"dateStr"` diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go index ad53f5d9f..474d829be 100644 --- a/backend/app/service/ssh.go +++ b/backend/app/service/ssh.go @@ -32,7 +32,6 @@ type ISSHService interface { UpdateByFile(value string) error Update(req dto.SSHUpdate) error GenerateSSH(req dto.GenerateSSH) error - AnalysisLog(req dto.SearchForAnalysis) (*dto.AnalysisRes, error) LoadSSHSecret(mode string) (string, 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: commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) 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: - 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") { @@ -320,9 +319,9 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) { case constant.StatusSuccess: commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) 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: - 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) @@ -337,89 +336,6 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) { 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) { if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil { return "", buserr.New("ErrHttpReqNotFound") @@ -529,147 +445,62 @@ func loadSSHData(command string, showCountFrom, showCountTo, currentYear int, qq 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 { var data dto.SSHHistory parts := strings.Fields(line) - t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0]) - if err != nil { - if len(parts) < 14 { - return data - } - data = dto.SSHHistory{ - DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), - AuthMode: parts[6], - User: parts[8], - Address: parts[10], - Port: parts[12], - 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, - } + index, dataStr := analyzeDateStr(parts) + if dataStr == "" { + return data } + data.DateStr = dataStr + data.AuthMode = parts[4+index] + data.User = parts[6+index] + data.Address = parts[8+index] + data.Port = parts[10+index] + data.Status = constant.StatusSuccess return data } func loadFailedAuthDatas(line string) dto.SSHHistory { var data dto.SSHHistory parts := strings.Fields(line) - t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0]) - if err != nil { - if len(parts) < 14 { - return data - } - data = dto.SSHHistory{ - DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), - AuthMode: parts[8], - User: parts[10], - Address: parts[11], - Port: parts[13], - Status: constant.StatusFailed, - } - } else { - if len(parts) < 12 { - 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, - } + index, dataStr := analyzeDateStr(parts) + if dataStr == "" { + return data } + data.DateStr = dataStr + if index == 2 { + data.User = parts[10] + } else { + data.User = parts[7] + } + data.AuthMode = parts[6+index] + data.Address = parts[9+index] + data.Port = parts[11+index] + data.Status = constant.StatusFailed if strings.Contains(line, ": ") { data.Message = strings.Split(line, ": ")[1] } return data } - func loadFailedSecureDatas(line string) dto.SSHHistory { var data dto.SSHHistory parts := strings.Fields(line) - t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0]) - if err != nil { - if len(parts) < 14 { - return data - } - data = dto.SSHHistory{ - DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), - AuthMode: parts[6], - User: parts[8], - Address: parts[10], - Port: parts[12], - Status: constant.StatusFailed, - } - } else { - if len(parts) < 12 { - 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, - } + index, dataStr := analyzeDateStr(parts) + if dataStr == "" { + return data } + data.DateStr = dataStr + if strings.Contains(line, " invalid ") { + data.AuthMode = parts[4+index] + index += 2 + } else { + data.AuthMode = parts[4+index] + } + data.User = parts[6+index] + data.Address = parts[8+index] + data.Port = parts[10+index] + data.Status = constant.StatusFailed if strings.Contains(line, ": ") { data.Message = strings.Split(line, ": ")[1] } @@ -699,3 +530,17 @@ func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time { } 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") +} diff --git a/backend/router/ro_host.go b/backend/router/ro_host.go index e6ad3f55d..a12580fe0 100644 --- a/backend/router/ro_host.go +++ b/backend/router/ro_host.go @@ -41,7 +41,6 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/generate", baseApi.GenerateSSH) hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret) hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs) - hostRouter.POST("/ssh/log/analysis", baseApi.AnalysisLog) hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile) hostRouter.POST("/ssh/operate", baseApi.OperateSSH) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index b0aa252fc..76b609513 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -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": { "post": { "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": { "type": "object", "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": { "type": "object", "properties": { diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 0a120cffe..4804481d6 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -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": { "post": { "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": { "type": "object", "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": { "type": "object", "properties": { diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 053b975ec..e0e0f38bd 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -2150,19 +2150,6 @@ definitions: totalCount: type: integer type: object - dto.SSHLogAnalysis: - properties: - address: - type: string - area: - type: string - failedCount: - type: integer - status: - type: string - successfulCount: - type: integer - type: object dto.SSHUpdate: properties: key: @@ -2200,22 +2187,6 @@ definitions: - ssl - sslType 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: properties: info: @@ -8966,30 +8937,6 @@ paths: summary: Load host SSH logs tags: - 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: post: consumes: diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 8cab4e3f5..165fa44b9 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -126,6 +126,3 @@ export const loadSecret = (mode: string) => { export const loadSSHLogs = (params: Host.searchSSHLog) => { return http.post(`/hosts/ssh/log`, params); }; -export const loadAnalysis = (params: Host.analysisSSHLog) => { - return http.post(`/hosts/ssh/log/analysis`, params, TimeoutEnum.T_40S); -}; diff --git a/frontend/src/views/host/ssh/log/analysis/index.vue b/frontend/src/views/host/ssh/log/analysis/index.vue deleted file mode 100644 index 8b820857e..000000000 --- a/frontend/src/views/host/ssh/log/analysis/index.vue +++ /dev/null @@ -1,259 +0,0 @@ - - - -