mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
ref: streamline SSH login log handling and remove unused functions (#13705)
This commit is contained in:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@@ -32,6 +33,7 @@ const (
|
||||
ResourceAlertInterval = 30
|
||||
CheckIntervalSec = 3
|
||||
LoadCheckIntervalMin = 5
|
||||
sshIPLoginWindow = 30 * time.Minute
|
||||
)
|
||||
|
||||
type AlertTaskHelper struct {
|
||||
@@ -512,10 +514,28 @@ func loadPanelLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
|
||||
func loadSSHLogin(alert dto.AlertDTO) {
|
||||
count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err)
|
||||
now := time.Now()
|
||||
failedWindow := time.Duration(alert.Cycle) * time.Minute
|
||||
loadWindow := failedWindow
|
||||
if loadWindow < sshIPLoginWindow {
|
||||
loadWindow = sshIPLoginWindow
|
||||
}
|
||||
location, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load timezone for ssh login logs: %v", err)
|
||||
location = time.Local
|
||||
}
|
||||
histories, err := loadSSHAlertHistories(defaultSSHLogDir, now.Add(-loadWindow), now, location)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||
}
|
||||
count, records := summarizeSSHLoginHistories(
|
||||
histories,
|
||||
now,
|
||||
failedWindow,
|
||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
||||
)
|
||||
isAlert := count >= int(alert.Count)
|
||||
if isAlert {
|
||||
params := []dto.Param{
|
||||
{
|
||||
@@ -531,12 +551,6 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
||||
}
|
||||
whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n")
|
||||
records, err := alertUtil.FindRecentSuccessLoginNotInWhitelist(30, whitelist)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err)
|
||||
}
|
||||
records = filterSSHLoginEntriesNotInWhitelist(records, whitelist)
|
||||
if len(records) > 0 {
|
||||
quota := strings.Join(records, "\n")
|
||||
params := []dto.Param{
|
||||
@@ -565,20 +579,6 @@ func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string)
|
||||
return filtered
|
||||
}
|
||||
|
||||
func filterSSHLoginEntriesNotInWhitelist(records []string, whitelist []string) []string {
|
||||
filtered := make([]string, 0, len(records))
|
||||
for _, record := range records {
|
||||
ip := record
|
||||
if idx := strings.Index(record, "-"); idx >= 0 {
|
||||
ip = record[:idx]
|
||||
}
|
||||
if !isIPInWhitelist(ip, whitelist) {
|
||||
filtered = append(filtered, record)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func isIPInWhitelist(ip string, whitelist []string) bool {
|
||||
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
||||
if targetIP == nil {
|
||||
@@ -1117,3 +1117,55 @@ func calculateMinutesDifference(newDate time.Time) int {
|
||||
minutesDifference := int(now.Sub(newDate).Minutes())
|
||||
return minutesDifference
|
||||
}
|
||||
|
||||
func loadSSHAlertHistories(
|
||||
baseDir string,
|
||||
startTime, endTime time.Time,
|
||||
location *time.Location,
|
||||
) ([]dto.SSHHistory, error) {
|
||||
fileList, err := listSSHLogFiles(baseDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
histories []dto.SSHHistory
|
||||
loadErr error
|
||||
)
|
||||
for _, file := range fileList {
|
||||
items, err := loadSSHHistoriesFromFile(file.Name, "", "", startTime, endTime, file.Year, location)
|
||||
if err != nil {
|
||||
loadErr = errors.Join(loadErr, fmt.Errorf("load SSH log file %s: %w", file.Name, err))
|
||||
continue
|
||||
}
|
||||
histories = append(histories, items...)
|
||||
}
|
||||
return histories, loadErr
|
||||
}
|
||||
|
||||
func summarizeSSHLoginHistories(
|
||||
histories []dto.SSHHistory,
|
||||
now time.Time,
|
||||
failedWindow time.Duration,
|
||||
whitelist []string,
|
||||
) (int, []string) {
|
||||
failedStartTime := now.Add(-failedWindow)
|
||||
successStartTime := now.Add(-sshIPLoginWindow)
|
||||
failedCount := 0
|
||||
var abnormalLogins []string
|
||||
|
||||
for _, item := range histories {
|
||||
switch item.Status {
|
||||
case constant.StatusFailed:
|
||||
if isSSHLogWithinTimeRange(item.Date, failedStartTime, now) {
|
||||
failedCount++
|
||||
}
|
||||
case constant.StatusSuccess:
|
||||
if !isSSHLogWithinTimeRange(item.Date, successStartTime, now) || isIPInWhitelist(item.Address, whitelist) {
|
||||
continue
|
||||
}
|
||||
abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", item.Address, item.Date.Format(constant.DateTimeLayout)))
|
||||
}
|
||||
}
|
||||
return failedCount, abnormalLogins
|
||||
}
|
||||
|
||||
+42
-18
@@ -40,6 +40,7 @@ import (
|
||||
const sshPath = "/etc/ssh/sshd_config"
|
||||
const defaultSSHPort = "22"
|
||||
const sshManagedMarker = "# config by 1panel"
|
||||
const defaultSSHLogDir = "/var/log"
|
||||
|
||||
type SSHService struct{}
|
||||
|
||||
@@ -668,13 +669,11 @@ func isSSHLogFileName(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) {
|
||||
func listSSHLogFiles(baseDir string) ([]sshFileItem, error) {
|
||||
var fileList []sshFileItem
|
||||
var data []dto.SSHHistory
|
||||
baseDir := "/var/log"
|
||||
fileItems, err := os.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
return 0, data, err
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range fileItems {
|
||||
if item.IsDir() || !isSSHLogFileName(item.Name()) {
|
||||
@@ -682,7 +681,7 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
|
||||
}
|
||||
info, err := item.Info()
|
||||
if err != nil {
|
||||
return 0, data, err
|
||||
return nil, err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
continue
|
||||
@@ -695,7 +694,15 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
|
||||
}
|
||||
fileList = append(fileList, sshFileItem{Name: itemPath, Year: info.ModTime().Year()})
|
||||
}
|
||||
fileList = sortFileList(fileList)
|
||||
return sortFileList(fileList), nil
|
||||
}
|
||||
|
||||
func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) {
|
||||
var data []dto.SSHHistory
|
||||
fileList, err := listSSHLogFiles(defaultSSHLogDir)
|
||||
if err != nil {
|
||||
return 0, data, err
|
||||
}
|
||||
|
||||
filter := ""
|
||||
if len(req.Info) != 0 {
|
||||
@@ -742,7 +749,7 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
|
||||
}
|
||||
|
||||
func (u *SSHService) CleanLog() error {
|
||||
return cleanSSHLogFiles("/var/log")
|
||||
return cleanSSHLogFiles(defaultSSHLogDir)
|
||||
}
|
||||
|
||||
func cleanSSHLogFiles(baseDir string) error {
|
||||
@@ -1290,20 +1297,11 @@ func loadSSHData(
|
||||
if err != nil {
|
||||
return datas, 0, 0
|
||||
}
|
||||
lines, err := loadSSHLogLines(filePath)
|
||||
histories, err := loadSSHHistoriesFromFile(filePath, status, filter, startTime, endTime, currentYear, nyc)
|
||||
if err != nil {
|
||||
return datas, 0, 0
|
||||
}
|
||||
items := collectSSHLogItems(lines, filter, status)
|
||||
for i := len(items) - 1; i >= 0; i-- {
|
||||
itemData := items[i].History
|
||||
if !matchSSHLogStatus(status, itemData.Status) || !checkIsStandard(itemData) {
|
||||
continue
|
||||
}
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
if !isSSHLogWithinTimeRange(itemData.Date, startTime, endTime) {
|
||||
continue
|
||||
}
|
||||
for _, itemData := range histories {
|
||||
if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) {
|
||||
itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx))
|
||||
datas = append(datas, itemData)
|
||||
@@ -1317,6 +1315,32 @@ func loadSSHData(
|
||||
return datas, successCount, failedCount
|
||||
}
|
||||
|
||||
func loadSSHHistoriesFromFile(
|
||||
filePath, status, filter string,
|
||||
startTime, endTime time.Time,
|
||||
currentYear int,
|
||||
location *time.Location,
|
||||
) ([]dto.SSHHistory, error) {
|
||||
lines, err := loadSSHLogLines(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := collectSSHLogItems(lines, filter, status)
|
||||
histories := make([]dto.SSHHistory, 0, len(items))
|
||||
for i := len(items) - 1; i >= 0; i-- {
|
||||
itemData := items[i].History
|
||||
if !matchSSHLogStatus(status, itemData.Status) || !checkIsStandard(itemData) {
|
||||
continue
|
||||
}
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, location)
|
||||
if !isSSHLogWithinTimeRange(itemData.Date, startTime, endTime) {
|
||||
continue
|
||||
}
|
||||
histories = append(histories, itemData)
|
||||
}
|
||||
return histories, nil
|
||||
}
|
||||
|
||||
func isSSHLogWithinTimeRange(itemTime, startTime, endTime time.Time) bool {
|
||||
if startTime.IsZero() || endTime.IsZero() {
|
||||
return true
|
||||
|
||||
@@ -2,13 +2,10 @@ package alert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
network "net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -24,7 +21,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/bark"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
@@ -536,133 +532,6 @@ func FindRecentSuccessLoginsNotInWhitelist(minutes int, whitelist []string) ([]m
|
||||
return abnormalLogs, nil
|
||||
}
|
||||
|
||||
func CountRecentFailedSSHLog(minutes uint, maxAllowed uint) (int, bool, error) {
|
||||
lines, err := grepSSHLog([]string{"Failed password", "Invalid user", "authentication failure"})
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
thresholdTime := time.Now().Add(-time.Duration(minutes) * time.Minute)
|
||||
count := 0
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
t, err := parseLogTime(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if t.After(thresholdTime) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count, count >= int(maxAllowed), nil
|
||||
}
|
||||
|
||||
func FindRecentSuccessLoginNotInWhitelist(minutes int, whitelist []string) ([]string, error) {
|
||||
lines, err := grepSSHLog([]string{"Accepted password", "Accepted publickey"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
thresholdTime := time.Now().Add(-time.Duration(minutes) * time.Minute)
|
||||
var abnormalLogins []string
|
||||
|
||||
whitelistMap := make(map[string]struct{}, len(whitelist))
|
||||
for _, ip := range whitelist {
|
||||
whitelistMap[ip] = struct{}{}
|
||||
}
|
||||
|
||||
ipRegex := re.GetRegex(re.AlertIPPattern)
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := parseLogTime(line)
|
||||
if err != nil || t.Before(thresholdTime) {
|
||||
continue
|
||||
}
|
||||
|
||||
match := ipRegex.FindStringSubmatch(line)
|
||||
if len(match) >= 2 {
|
||||
ip := match[1]
|
||||
if _, ok := whitelistMap[ip]; !ok {
|
||||
abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", ip, t.Format("2006-01-02 15:04:05")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return abnormalLogins, nil
|
||||
}
|
||||
|
||||
func findGrepPath() (string, error) {
|
||||
path, err := exec.LookPath("grep")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("grep not found in PATH: %w", err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func grepSSHLog(keywords []string) ([]string, error) {
|
||||
logFiles := []string{"/var/log/secure", "/var/log/auth.log"}
|
||||
var results []string
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
grepPath, err := findGrepPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find grep failed: %w", err)
|
||||
}
|
||||
|
||||
for _, logFile := range logFiles {
|
||||
if _, err := os.Stat(logFile); err != nil {
|
||||
continue
|
||||
}
|
||||
for _, keyword := range keywords {
|
||||
cmd := exec.Command(grepPath, "-a", keyword, logFile)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("read log file fail [%s]: %w", logFile, err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
if _, exists := seen[line]; !exists {
|
||||
results = append(results, line)
|
||||
seen[line] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func parseLogTime(line string) (time.Time, error) {
|
||||
if len(line) < 15 {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
timeStr := line[:15]
|
||||
parsedTime, err := time.ParseInLocation("Jan 2 15:04:05", timeStr, time.Local)
|
||||
if err != nil {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
return parsedTime.AddDate(time.Now().Year(), 0, 0), nil
|
||||
}
|
||||
|
||||
func getNodeName(agentInfo *dto.AgentInfo) string {
|
||||
var nodeName string
|
||||
if agentInfo != nil && agentInfo.NodeName != "" {
|
||||
|
||||
Reference in New Issue
Block a user