运维脚本: 实时监测登录日志
引言
-
背景介绍:在服务器的运维管理中,及时监控系统的登录日志对保障系统的安全至关重要。通过实时监控登录日志,运维人员可以发现潜在的异常登录行为,防止系统被非法访问。
-
问题引入:如何实现实时监控登录日志,并及时响应潜在的安全风险?
实时监控登录日志的意义
-
安全性:通过监控登录日志,可以迅速发现恶意登录、暴力破解等异常行为。
-
合规性:确保满足各种合规要求,记录所有用户的登录行为。
解决方案概述
-
监控目标:关注登录日志中的关键信息,例如登录时间、IP 地址、用户名、登录方式等。
-
技术选型:通过编写 Bash 脚本,结合inotify、awk、grep 等工具,来实现对日志文件的实时监控与分析。
脚本实现原理
-
实时监控:利用 inotify 命令动态监控日志文件的变动,并结合 sed 命令实时提取和输出新增的登录日志。
-
日志筛选:通过 grep 等工具过滤出登录失败、异常登录等相关信息。
-
报警机制:脚本可以配置成在监控到异常行为时,自动发送通知邮件
脚本示例
#!/bin/bash # 作者: 阿杰 # 用途: 实时检测登录日志,统计异常登录 # 脚本名称: watch_secure.sh # 用法: bash watch_seacure.sh # 日志记录 log_err() { printf "[$(date +'%Y-%m-%dT%H:%M:%S')]: \033[31mERROR: \033[0m$@\n" } log_info() { printf "[$(date +'%Y-%m-%dT%H:%M:%S')]: \033[32mINFO: \033[0m$@\n" } log_warning() { printf "[$(date +'%Y-%m-%dT%H:%M:%S')]: \033[33mWARNING: \033[0m$@\n" } # 初始化Map declare -A secureMap init() { # 行数记录文件 line_file_name="conf/line_file.txt" # inode存储文件 inode_file="conf/inode.txt" # 认证失败文件记录 ssh_auth_failed_file="conf/ssh_auth_failed.csv" # 文件列表 file_array=("$line_file_name" "$inode_file" "$ssh_auth_failed_file") # inode 文件状态 inode_file_status=0 # 控制是否进行写入 0为可写,1为不可写 write_status=1 oneSecureKey="" { if [ ! -d "conf" ];then mkdir conf fi # 检查文件是否存在 for file in ${file_array[@]};do check_file_exists $file done line=$(cat $line_file_name) if [ -z "$line" ];then line=0 fi # 认证失败文件第一次创建 if [ $(wc -l < $ssh_auth_failed_file) -eq 0 ];then # 时间以月天为单位(None为空账号或不存在账号) echo "登录认证失败时间,源IP地址,登录账号,连接认证失败次数" > $ssh_auth_failed_file fi } file_name="/var/log/secure" if [ -z "$(rpm -qa | grep 'inotify-tools')" ];then yum install -y inotify-tools > /dev/null 2>&1 if [ $? -ne 0 ];then log_err "[init] inotify-tools 安装失败!" fi fi } # 检查文件是否存在,不存在则创建 check_file_exists() { local file_name=$1 if [ ! -f "$file_name" ];then touch $file_name if [ $? -ne 0 ];then log_err "[check_file_exists] file: $file_name 文件创建失败!" fi fi } # 监听文件事件 watch_file() { inotifywait -mrq --format '%e' --event create,delete,modify $file_name | while read event ;do case "$event" in MODIFY) start_read_file ;; # 文件被删除或重新创建 CREATE|DELETE) # 重置文件行数 line=0 > $line_file_name check ;; *) log_warning "[watch_file] watch file event: $event" ;; esac done } # 只读一行 read_line_file() { ((line++)) echo $line > $line_file_name # 不是指定数据退出 if [ $(sed -n "$line p" $file_name | grep 'pam_unix(sshd:auth): authentication failure;' | wc -l ) -ne 1 ];then return fi # 控制是否进行写入 write_status=0 oneSecureKey=$(sed -n "$line p" $file_name |awk -v dateNow=$(date +"%Y") '{ split($0,rhost,"rhost=") split(rhost[2],rhost," ") split($0,user," user=") if (length(user[2])==0) { user[2]="None" } print dateNow":"$1":"$2","rhost[1]","user[2] }') log_info "[read_line_file] line: $line data:[$oneSecureKey]" send_map $oneSecureKey } # 往MAP中塞入数据 send_map() { local key=$1 if [ -n ${secureMap[$key]} ];then secureMap[$key]=`expr ${secureMap[$key]} + 1` else secureMap[$key]=1 fi } wirte_all_secure() { for key in ${!secureMap[@]};do write_one_secure $key done } write_one_secure() { local key="$@" local data=$(grep -w -n "$key" $ssh_auth_failed_file) if [ -n "$data" ];then local i=$(echo $data | awk -F: '{print $1}') local a=$(echo $data | awk -F, '{print $NF}') sed -i "${i} s#$a#${secureMap[$key]}#" $ssh_auth_failed_file if [ $? -ne 0 ];then log_err "[write_secure] 写 $ssh_auth_failed_file 文件失败! data:[$key,${secureMap[$key]}]" fi else # 新数据 echo "$key,${secureMap[$key]}" >> $ssh_auth_failed_file if [ $? -ne 0 ];then log_err "[write_secure] 写 $ssh_auth_failed_file 文件失败! data:[$key,${secureMap[$key]}]" fi fi log_info "[write_secure] line: $line status: $write_status data:[$key,${secureMap[$key]}]" } # 启动前应先检查是否读取过 check() { # 检查预存Inode是否一致 check_secure_file_inode } # 检查登录日志Inode是否一致 check_secure_file_inode() { inode=$(ls -i $file_name | awk '{print $1}') inode_file_data="$(cat $inode_file)" if [ -n "$inode_file_data" ]; then if [ $inode -ne $inode_file_data ];then log_warning "[check_secure_file_inode] secure file inode is inconsistency" # inode不一致,重置 echo "$inode" > $inode_file inode_file_status=1 else inode_file_status=0 fi else # 第一次读取 echo "$inode" > $inode_file inode_file_status=1 fi } # 开始读取文件 start_read_file() { # 第一次读取 if [ $inode_file_status -eq 1 ] ;then # 使用循环将历史内容读取 while true;do if [ $line -eq $(wc -l < $file_name) ];then break fi read_line_file done wirte_all_secure elif [ $line -ne $(wc -l < $file_name) ];then # 使用循环将行数对齐 while true;do if [ $line -eq $(wc -l < $file_name) ];then break fi read_line_file if [ $write_status -eq 0 ];then write_one_secure $oneSecureKey fi # 状态设置为1 write_status=1 done # else # read_line_file # if [ $write_status -eq 0 ];then # write_one_secure $oneSecureKey # fi # # 状态设置为1 # write_status=1 fi } test_main() { init check_secure_file_inode } main() { # 初始化 init # 内容检查 check start_read_file log_info "[main] watch secure startd" watch_file } main