服务器运维(十六)vlang语言linuxSSH日志分析——东方仙盟炼气期

Vlang 的实现,严格保留原逻辑(中英文日志解析、重复记录合并、数据库交互、备份清理),适配 Vlang 语法特性和生态:

v

复制代码
module main

import os
import time
import strconv
import regex
import db.mysql
import sync
import encoding.utf8

// ---------------------- 配置参数 ----------------------
const (
	LOG_PATH    = '/var/log/secure'
	BACKUP_DIR  = '/CyberWin/pro/wlzc_firewallsafesniffer/bak_log/ssh_log'
	KEEP_DAYS   = 7
	DB_CONFIG = mysql.Config{
		host: '在哪里'
		dbname: '这个方便查询'
		user: '你是爱找别人麻烦'
		password: '你大爷不是我'
		port: 87836 // Vlang mysql 端口为 int 类型
		charset: 'utf8mb4'
	}
)

// 月份映射(中英文)
const (
	english_month_map = {
		'JAN': '01', 'FEB': '02', 'MAR': '03', 'APR': '04'
		'MAY': '05', 'JUN': '06', 'JUL': '07', 'AUG': '08'
		'SEP': '09', 'OCT': '10', 'NOV': '11', 'DEC': '12'
	}
	chinese_month_map = {
		'1月': '01', '2月': '02', '3月': '03', '4月': '04', '5月': '05', '6月': '06'
		'7月': '07', '8月': '08', '9月': '09', '10月': '10', '11月': '11', '12月': '12'
		'一月': '01', '二月': '02', '三月': '03', '四月': '04', '五月': '05', '六月': '06'
		'七月': '07', '八月': '08', '九月': '09', '十月': '10', '十一月': '11', '十二月': '12'
	}
)

// 日志数据结构(对应 PHP 数组)
struct LogData {
	log_time   string
	ip         string
	port       string
	action     string
	username   string
	detail     string
	risk_level int
}

// ---------------------- 工具函数 ----------------------

// 清理过期备份(保留 KEEP_DAYS 天)
fn clean_old_backups() {
	if !os.exists(BACKUP_DIR) {
		os.mkdir_all(BACKUP_DIR) or {
			eprintln('创建备份目录失败: ${err}')
			return
		}
	}

	now := time.now().unix()
	files := os.ls(BACKUP_DIR) or {
		eprintln('读取备份目录失败: ${err}')
		return
	}

	for file in files {
		file_path := os.join_path(BACKUP_DIR, file)
		if os.is_file(file_path) {
			file_mtime := os.file_mtime(file_path) or { continue }
			if (now - file_mtime.unix()) > KEEP_DAYS * 86400 {
				os.rm(file_path) or {
					eprintln('删除过期备份失败 [${file_path}]: ${err}')
				}
			}
		}
	}
}

// 英文月份转数字(兼容大小写)
fn english_month_to_num(month string) ?string {
	key := month.to_upper().substr(0, 3)
	return english_month_map[key] or { error('未知英文月份: ${month}') }
}

// 中文月份转数字
fn chinese_month_to_num(month string) ?string {
	return chinese_month_map[month] or { error('未知中文月份: ${month}') }
}

// 构建英文日志数据(对应 PHP build_data)
fn build_data(match []string, year string) ?LogData {
	mut data := LogData{ risk_level: 1 }
	log_time_str := match[1]

	// 解析英文时间格式:Nov 30 19:25:23
	time_re := regex.regex_opt(r'^([A-Za-z]{3}) (\d+) (\d{2}:\d{2}:\d{2})$') or {
		return error('时间正则编译失败')
	}
	time_match := time_re.find_all_str(log_time_str, 4)
	if time_match.len != 4 {
		return error('时间格式不匹配: ${log_time_str}')
	}

	// 月份转换
	month := english_month_to_num(time_match[1]) ?
	day := time_match[2].zfill(2) // 补零(如 5 → 05)
	time_part := time_match[3]

	// 构建标准时间字符串
	std_time_str := '${year}-${month}-${day} ${time_part}'
	log_time := time.parse(std_time_str, '2006-01-02 15:04:05') or {
		return error('时间解析失败: ${std_time_str}')
	}
	data.log_time = log_time.format('2006-01-02 15:04:05')

	// 根据匹配场景填充字段
	action := match match[0] {
		// 场景1: Failed password
		contains('Failed password') {
			data.risk_level = 3
			data.detail = '暴力破解尝试:用户${match[2]},IP${match[3]},端口${match[4]}'
			data.username = match[2]
			data.ip = match[3]
			data.port = match[4]
			'FAILED_PASSWORD'
		}
		// 场景2: Connection closed by preauth(IPv4)
		contains('Connection closed by') && !match[3].contains(':') {
			data.risk_level = 2
			data.detail = '未认证连接关闭:IP${match[2]},端口${match[3]}'
			data.username = match[2] // 原PHP逻辑:CONN_CLOSED_PREAUTH场景下后续交换ip和username
			data.ip = match[2]
			data.port = match[3]
			'CONN_CLOSED_PREAUTH'
		}
		// 场景3: Connection closed by preauth(IPv6)
		contains('Connection closed by') && match[3].contains(':') {
			data.risk_level = 2
			data.detail = '未认证IPv6连接关闭:IP${match[2]},端口${match[3]}'
			data.username = match[2]
			data.ip = match[2]
			data.port = match[3]
			'CONN_CLOSED_PREAUTH'
		}
		// 场景4: SSH协议错误
		contains('error:') {
			data.risk_level = 2
			data.detail = 'SSH协议错误:${match[2]}'
			'PROTOCOL_ERROR'
		}
		// 场景5: Accepted password
		contains('Accepted password') {
			data.risk_level = 1
			data.detail = '成功登录:用户${match[2]},IP${match[3]},端口${match[4]}'
			data.username = match[2]
			data.ip = match[3]
			data.port = match[4]
			'ACCEPTED_PASSWORD'
		}
		else {
			return error('未知日志场景: ${match[0]}')
		}
	}

	data.action = action
	return data
}

// 构建中文日志数据(对应 PHP build_data中文)
fn build_data_chinese(match []string, year string) ?LogData {
	mut data := LogData{ risk_level: 1 }
	log_time_str := match[1]

	// 解析中文时间格式:11月 30 18:00:00 或 十一月 30 18:00:00
	time_re := regex.regex_opt(r'^(\S+月) (\d+) (\d{2}:\d{2}:\d{2})$') or {
		return error('中文时间正则编译失败')
	}
	time_match := time_re.find_all_str(log_time_str, 4)
	if time_match.len != 4 {
		return error('中文时间格式不匹配: ${log_time_str}')
	}

	// 月份转换
	month := chinese_month_to_num(time_match[1]) ?
	day := time_match[2].zfill(2)
	time_part := time_match[3]

	// 构建标准时间字符串
	std_time_str := '${year}-${month}-${day} ${time_part}'
	log_time := time.parse(std_time_str, '2006-01-02 15:04:05') or {
		return error('中文时间解析失败: ${std_time_str}')
	}
	data.log_time = log_time.format('2006-01-02 15:04:05')

	// 根据匹配场景填充字段
	action := match match[0] {
		// 场景1: Failed password
		contains('Failed password') {
			data.risk_level = 3
			data.detail = '暴力破解尝试:用户${match[2]},IP${match[3]},端口${match[4]}'
			data.username = match[2]
			data.ip = match[3]
			data.port = match[4]
			'FAILED_PASSWORD'
		}
		// 场景2: Connection closed by preauth(IPv4)
		contains('Connection closed by') && !match[3].contains(':') {
			data.risk_level = 2
			data.detail = '未认证连接关闭:IP${match[2]},端口${match[3]}'
			data.username = match[2]
			data.ip = match[2]
			data.port = match[3]
			'CONN_CLOSED_PREAUTH'
		}
		// 场景3: Connection closed by preauth(IPv6)
		contains('Connection closed by') && match[3].contains(':') {
			data.risk_level = 2
			data.detail = '未认证IPv6连接关闭:IP${match[2]},端口${match[3]}'
			data.username = match[2]
			data.ip = match[2]
			data.port = match[3]
			'CONN_CLOSED_PREAUTH'
		}
		// 场景4: SSH协议错误
		contains('error:') {
			data.risk_level = 2
			data.detail = 'SSH协议错误:${match[2]}'
			'PROTOCOL_ERROR'
		}
		// 场景5: Accepted password
		contains('Accepted password') {
			data.risk_level = 1
			data.detail = '成功登录:用户${match[2]},IP${match[3]},端口${match[4]}'
			data.username = match[2]
			data.ip = match[3]
			data.port = match[4]
			'ACCEPTED_PASSWORD'
		}
		else {
			return error('未知中文日志场景: ${match[0]}')
		}
	}

	data.action = action
	return data
}

// 解析英文日志行(对应 PHP parse_log_line)
fn parse_log_line(line string, year string) ?LogData {
	// 编译所有英文日志正则
	patterns := [
		// 场景1: Failed password
		r'^([A-Za-z]{3} \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Failed password for (\S+) from (\S+) port (\d+) ssh2$',
		// 场景2: Connection closed by preauth(IPv4)
		r'^([A-Za-z]{3} \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Connection closed by (\S+) port (\d+) \[preauth\]$',
		// 场景3: Connection closed by preauth(IPv6)
		r'^([A-Za-z]{3} \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Connection closed by ([0-9a-fA-F:]+) port (\d+) \[preauth\]$',
		// 场景4: SSH协议错误
		r'^([A-Za-z]{3} \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: error: (.+)$',
		// 场景5: Accepted password
		r'^([A-Za-z]{3} \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Accepted password for (\S+) from (\S+) port (\d+) ssh2$',
	]

	// 逐个匹配正则
	for pat in patterns {
		re := regex.regex_opt(pat) or {
			eprintln('正则编译失败: ${pat}')
			continue
		}
		match := re.find_all_str(line, 6)
		if match.len > 0 {
			return build_data(match, year)
		}
	}

	return error('无匹配的英文日志模式')
}

// 解析中文日志行(对应 PHP parse_log_line中文)
fn parse_log_line_chinese(line string, year string) ?LogData {
	// 编译所有中文日志正则
	patterns := [
		// 场景1: Failed password
		r'^((?:\d+|[一二三四五六七八九十]+)月 \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Failed password for (\S+) from (\S+) port (\d+) ssh2$',
		// 场景2: Connection closed by preauth(IPv4)
		r'^((?:\d+|[一二三四五六七八九十]+)月 \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Connection closed by (\S+) port (\d+) \[preauth\]$',
		// 场景3: Connection closed by preauth(IPv6)
		r'^((?:\d+|[一二三四五六七八九十]+)月 \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Connection closed by ([0-9a-fA-F:]+) port (\d+) \[preauth\]$',
		// 场景4: SSH协议错误
		r'^((?:\d+|[一二三四五六七八九十]+)月 \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: error: (.+)$',
		// 场景5: Accepted password
		r'^((?:\d+|[一二三四五六七八九十]+)月 \d+ \d{2}:\d{2}:\d{2}) \S+ sshd\[\d+\]: Accepted password for (\S+) from (\S+) port (\d+) ssh2$',
	]

	// 逐个匹配正则
	for pat in patterns {
		re := regex.regex_opt(pat) or {
			eprintln('中文正则编译失败: ${pat}')
			continue
		}
		match := re.find_all_str(line, 6)
		if match.len > 0 {
			return build_data_chinese(match, year)
		}
	}

	return error('无匹配的中文日志模式')
}

// 解析备份日志并入库(对应 PHP parse_backup_log)
fn parse_backup_log(file_path string, db &mysql.Conn) ? {
	// 从文件名提取年份
	filename := os.file_name(file_path)
	year_re := regex.regex_opt(r'secure_(\d{4})') or {
		return error('年份正则编译失败')
	}
	year_match := year_re.find_all_str(filename, 2)
	year := if year_match.len == 2 { year_match[1] } else { time.now().format('2006') }

	// 打开日志文件
	file := os.open(file_path) or {
		return error('打开日志文件失败 [${file_path}]: ${err}')
	}
	defer file.close()

	// 逐行读取日志
	for line in file.lines() {
		line = line.trim_space()
		if line == '' {
			continue
		}

		// 尝试解析(先英文后中文)
		mut data := match parse_log_line(line, year) {
			Ok(d) { d }
			Err(e) {
				// 英文解析失败,尝试中文
				match parse_log_line_chinese(line, year) {
					Ok(d) { d }
					Err(_) {
						// 无匹配,跳过
						continue
					}
				}
			}
		}

		// 原PHP逻辑:CONN_CLOSED_PREAUTH场景交换port、ip、username
		if data.action == 'CONN_CLOSED_PREAUTH' {
			temp_port := data.port
			data.port = data.ip
			data.ip = data.username
			data.username = '' // 原逻辑中该场景username无实际意义
		}

		// 生成分钟级去重键(对应 PHP date('YmdHi'))
		log_time := time.parse(data.log_time, '2006-01-02 15:04:05') or {
			eprintln('日志时间解析失败: ${data.log_time}')
			continue
		}
		minute_key := log_time.format('200601021504')

		// 检查重复记录
		rows := db.query_row(
			'SELECT cyber_id FROM cybersafe_ssh_events WHERE ip = ? AND action = ? AND DATE_FORMAT(log_time, "%Y%m%d%H%i") = ? LIMIT 1',
			data.ip, data.action, minute_key,
		) or {
			eprintln('查询重复记录失败: ${err}')
			continue
		}

		mut cyber_id := 0
		if rows.scan(&cyber_id) == nil {
			// 存在重复,更新计数
			_, err := db.exec(
				'UPDATE cybersafe_ssh_events SET count = count + 1 WHERE cyber_id = ?',
				cyber_id,
			)
			if err != nil {
				eprintln('更新重复记录失败: ${err}')
			}
		} else {
			// 不存在,插入新记录
			_, err := db.exec(
				'INSERT INTO cybersafe_ssh_events (log_time, ip, port, action, username, detail, risk_level, count) VALUES (?, ?, ?, ?, ?, ?, ?, 1)',
				data.log_time, data.ip, data.port, data.action, data.username, data.detail, data.risk_level,
			)
			if err != nil {
				eprintln('插入新记录失败: ${err}')
			}
		}
	}

	println('日志解析完成: ${file_path}')
}

// ---------------------- 主执行流程 ----------------------
fn main() {
	// 连接数据库
	db := mysql.connect(DB_CONFIG) or {
		eprintln('数据库连接失败: ${err}')
		return
	}
	defer db.close()

	// 测试用备份文件(与原PHP一致)
	backup_file := '/CyberWin/pro/wlzc_firewallsafesniffer/bak_log/ssh_log/secure_20251130183113'

	// 检查文件是否存在
	if !os.exists(backup_file) {
		eprintln('备份文件不存在: ${backup_file}')
		return
	}

	// 解析日志入库
	if err := parse_backup_log(backup_file, &db); err != nil {
		eprintln('日志解析失败: ${err}')
		return
	}

	// 清理过期备份(原PHP注释掉,此处保留可选)
	// clean_old_backups()

	println('处理成功!文件:${backup_file}')
}

关键说明(严格对齐原 PHP 逻辑)

  1. 配置参数 :完全保留原 PHP 的路径、数据库配置,适配 Vlang mysql.Config 结构体(端口转为 int 类型)。
  2. 日志解析
    • 分别实现 parse_log_line(英文日志)和 parse_log_line_chinese(中文日志),正则表达式与原 PHP 完全一致。
    • 保留中英文月份转换、时间格式标准化(2006-01-02 15:04:05)。
    • 严格还原 CONN_CLOSED_PREAUTH 场景的字段交换逻辑(port = ipip = username)。
  3. 数据库交互
    • 使用 Vlang 标准库 db.mysql 连接 MySQL,SQL 语句与原 PHP 完全一致。
    • 保留分钟级去重逻辑(DATE_FORMAT(log_time, "%Y%m%d%H%i"))和重复记录合并(更新 count)。
  4. 文件操作
    • 逐行读取日志文件,跳过空行,错误处理与原 PHP 一致。
    • clean_old_backups 函数保留原逻辑,清理超过 7 天的备份文件。

依赖与编译运行

  1. 依赖 :Vlang 内置 db.mysqlregexos 等模块,无需额外安装第三方依赖。

  2. 编译

    bash

    运行

    复制代码
    v build -prod ssh_log_parser.v  # 生成可执行文件
  3. 运行

    • 确保 MySQL 服务正常运行,配置的数据库、表(cybersafe_ssh_events)已存在。
    • 确保日志文件路径、备份目录权限可读写。
    • 执行:./ssh_log_parser

注意事项

  1. 编码兼容:Vlang 字符串默认 UTF-8,中文日志解析无编码问题(需确保日志文件为 UTF-8 编码)。
  2. 正则匹配 :使用 Vlang regex 模块,正则表达式与原 PHP 完全一致,确保匹配逻辑不变。
  3. 错误处理:保留原 PHP 的容错逻辑(解析失败的日志行直接跳过),同时增加详细的错误打印便于调试。
  4. 时间格式 :Vlang 时间解析使用 Golang 风格的格式字符串(2006-01-02 15:04:05),已在代码中适配。

表结构说明(与原 PHP 兼容)

确保数据库中 cybersafe_ssh_events 表结构如下(参考原 PHP 插入字段):

sql

复制代码
CREATE TABLE `cybersafe_ssh_events` (
  `cyber_id` int(11) NOT NULL AUTO_INCREMENT,
  `log_time` datetime NOT NULL,
  `ip` varchar(64) NOT NULL,
  `port` varchar(16) NOT NULL,
  `action` varchar(32) NOT NULL,
  `username` varchar(64) DEFAULT '',
  `detail` text NOT NULL,
  `risk_level` tinyint(4) NOT NULL,
  `count` int(11) NOT NULL DEFAULT 1,
  PRIMARY KEY (`cyber_id`),
  KEY `idx_ip_action_time` (`ip`,`action`,`log_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

作为服务器远程管理的核心协议,其日志记录了所有 SSH 连接的完整轨迹,包括登录行为、认证结果、异常操作等关键信息。对 SSH 日志进行常态化分析,是服务器安全防护体系的重要组成部分,兼具攻击检测、合规审计、故障排查、风险预警四大核心价值,具体重要意义如下:

  • 暴力破解识别 :通过统计短时间内来自同一 IP 的Failed password记录,可快速定位暴力破解源,及时封禁 IP 或限制登录频率,避免密码被破解。

  • 异常登录拦截 :当日志中出现非信任 IP 的Accepted password记录、异地登录、非常规时段登录(如深夜批量登录)时,可能是账号泄露或非法入侵的信号,可立即触发告警并核查操作。

  • 协议漏洞利用监测 :日志中error:开头的协议错误记录,可能是攻击者尝试利用 SSH 协议漏洞(如 SSH 弱加密算法、漏洞扫描)的痕迹,提前发现可避免漏洞被利用。

  • 还原攻击路径 :通过日志中的log_time(时间)、ip(来源 IP)、username(登录账号)、port(端口)等字段,可完整还原攻击者的登录时间、操作账号、来源地址,甚至关联到攻击所用设备。

  • 明确责任归属:区分合法管理员操作与非法入侵行为,若为内部账号泄露,可定位账号责任人;若为外部攻击,可固定攻击 IP 等证据,为追责或上报提供依据。

  • 合规审计需求:金融、政务、医疗等行业的合规标准(如等保 2.0、PCI - DSS)明确要求留存服务器操作日志并定期审计,SSH 日志分析是满足合规要求的必要环节。

  • 登录失败故障定位 :用户反馈 SSH 登录失败时,通过日志可区分是密码错误(Failed password)、端口占用、密钥配置错误,还是服务异常(如 sshd 进程崩溃),避免盲目排查。

  • 服务异常预警 :日志中频繁出现Connection closed by preauth(未认证连接关闭),可能是 SSH 服务配置不当(如超时设置过短)或网络不稳定,提前调整可提升服务可用性。

  • 资源占用分析:大量无效 SSH 连接尝试会占用服务器端口和 CPU 资源,通过日志统计连接频率,可优化防火墙规则,减轻服务器负载。

  • 弱口令与账号治理:统计高频登录失败的账号,可发现弱口令账号,强制要求更换复杂密码;清理长期未使用的 "僵尸账号",减少攻击面。

  • 访问控制优化:基于日志中高频合法登录 IP,配置 SSH 白名单,禁止非信任 IP 访问;关闭不必要的 SSH 端口,仅开放业务所需端口。

  • 安全配置加固:根据日志中出现的风险行为(如使用弱加密算法登录),调整 sshd 配置文件(如禁用密码登录、强制使用 SSH 密钥、关闭过时加密算法)。

  • 扫描行为预警 :短时间内来自多个 IP 的CONN_CLOSED_PREAUTH(未认证连接关闭)记录,可能是攻击者的批量扫描行为,预示后续可能出现大规模攻击。

  • 账号异常使用预警:同一账号在不同 IP 频繁登录,可能是账号密码已被泄露,需立即冻结账号并重置密码。

  1. 实时发现恶意攻击,阻断入侵行为

    SSH 是网络攻击者的重点目标,攻击者常通过暴力破解、弱口令攻击、漏洞利用等方式尝试入侵。日志分析能精准捕捉这类恶意行为:

  2. 追溯安全事件,定位攻击源头与责任

    当服务器发生数据泄露、恶意篡改等安全事件时,SSH 日志是事后溯源的关键 "证据链",具备不可替代的审计价值:

  3. 排查系统故障,保障服务稳定运行

    SSH 日志不仅记录安全事件,还包含大量系统运行状态信息,可用于快速排查远程管理相关的故障:

  4. 优化安全策略,构建纵深防御体系

    长期的 SSH 日志分析能沉淀出服务器的安全基线,反向指导安全策略优化,从被动防御转向主动防护:

  5. 预警潜在风险,防范未然

    很多攻击行为在爆发前会有明显的 "试探性操作",SSH 日志分析能捕捉这些前兆,提前规避风险:

结尾交付物提议

相关推荐
吕了了1 小时前
113 隐藏此电脑中的常用文件夹
运维·windows·系统
不染尘.1 小时前
计算机网络评价指标和封包解包
服务器·网络·计算机网络
p***92481 小时前
Nginx location 和 proxy_pass 配置详解
服务器·网络·nginx
L***B5681 小时前
Nginx代理到https地址忽略证书验证配置
运维·nginx·https
源梦想1 小时前
绝地幸存者H5割草网页小游戏Linux部署演示
linux·运维·服务器
凑齐六个字吧1 小时前
单细胞LIANA受配体分析框架学习
linux·服务器·windows
拾心212 小时前
【云运维】K8s管理(二)
运维·容器·kubernetes
e***58232 小时前
Nginx 配置前端后端服务
运维·前端·nginx
qq_526099132 小时前
实时工业图像采集卡 | 低延迟传输,满足自动化生产线需求
运维·自动化