Skip to content

Syslog 基础篇:协议解剖与 rsyslog 配置

安全日志生态的“通用语”——从协议格式到配置实战,掌握日志采集的底层原理

引言:为什么安全产品离不开 syslog?

在现代安全基础设施中,SIEM(安全信息与事件管理)、HIDS(主机入侵检测系统)、NIDS(网络入侵检测系统)都需要集中采集各类设备的日志。而 syslog 正是这个生态的“通用语”:

  • 防火墙、路由器、交换机 → syslog
  • Linux 服务器的 sshdsudocron → syslog
  • 堡垒机、数据库审计 → syslog

作为安全开发者,理解 syslog 协议细节,是正确解析日志、避免漏报误报的基础。

syslog 协议格式深度解剖

syslog 主要有两个 RFC 标准:RFC 3164(传统格式,BSD-syslog)和 RFC 5424(增强格式)。安全场景中两者并存,解析时需要兼容处理。

完整格式 vs 原始格式

一条典型的 RFC 3164 syslog 消息:

log
<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8

拆解为:

部分示例含义
PRI<34>Facility + Priority 编码值
HEADEROct 11 22:14:15 mymachine时间戳 + 主机名
MSGsu: 'su root' failed...标签(TAG) + 内容(CONTENT)

PRI 计算与反解(Golang 解析示例)

PRI = Facility × 8 + Priority

  • Facility 范围:0-23(设备类型,如 kern=0, auth=4, daemon=3)
  • Priority 范围:0-7(严重级别,0=emerg, 3=err, 6=info)

示例计算<34>34 / 8 = 42 → Facility=4(auth), Priority=2(crit)

Golang 解析 demo

go
package main

import (
    "fmt"
    "strconv"
    "strings"
)

// parsePRI 解析 PRI 字段,返回 facility 和 priority
func parsePRI(priStr string) (facility, priority int, err error) {
    priStr = strings.Trim(priStr, "<>")
    pri, err := strconv.Atoi(priStr)
    if err != nil {
        return 0, 0, err
    }
    facility = pri / 8
    priority = pri % 8
    return facility, priority, nil
}

func main() {
    facility, priority, _ := parsePRI("<34>")
    fmt.Printf("Facility: %d (auth), Priority: %d (crit)\n", facility, priority)
}

HEADER:时间戳与主机名

RFC 3164 时间戳格式Mmm dd hh:mm:ss(无年份,安全解析时需补充)

go
import "time"

// parseRFC3164Time 解析 RFC 3164 时间戳(需补充年份)
func parseRFC3164Time(timestamp string, currentYear int) (time.Time, error) {
    layout := "Jan 2 15:04:05"
    t, err := time.Parse(layout, timestamp)
    if err != nil {
        return time.Time{}, err
    }
    return time.Date(currentYear, t.Month(), t.Day(),
        t.Hour(), t.Minute(), t.Second(), 0, time.Local), nil
}

主机名:可能是短主机名或 FQDN,用于标识日志来源。

MSG:TAG + CONTENT

  • TAG:通常是进程名,如 susshdkernel
  • CONTENT:具体日志内容
go
// splitTagContent 分割 TAG 和 CONTENT
func splitTagContent(msg string) (tag, content string) {
    parts := strings.SplitN(msg, ":", 2)
    if len(parts) == 2 {
        return parts[0], strings.TrimSpace(parts[1])
    }
    return msg, ""
}

Facility 与 Priority:安全日志的分类与分级

安全相关的 Facility

Facility安全含义
kern0内核日志:网络过滤、SELinux、iptables 拒绝
auth4认证日志:login、su、sudo(非特权)
authpriv10特权认证日志:SSH 登录、密码修改
daemon3守护进程:各类服务的安全事件
local0 ~ local716-23自定义:应用层安全日志

Priority 到告警级别的映射

Priority安全告警级别典型事件
emerg0🔴 严重系统不可用
alert1🔴 高危需要立即处理(如 root 被暴力破解)
crit2🟠 高危关键服务失败(如 PAM 崩溃)
err3🟡 中危认证失败、权限错误
warning4🟡 低危可疑行为但不致命
notice5🔵 信息重要但正常的操作
info6🔵 调试常规访问日志
debug7⚪ 忽略调试信息

rsyslog 配置实战

基础配置结构

rsyslog 配置文件 /etc/rsyslog.conf 核心语法:

apache
# 模块加载
module(load="imudp")      # 加载 UDP 输入模块
input(type="imudp" port="514")

# 模板定义
template(name="DynamicFile" type="string"
         string="/var/log/%HOSTNAME%/%PROGRAMNAME%.log")

# 选择器规则
auth,authpriv.*         /var/log/auth.log
kern.*                  /var/log/kern.log
*.emerg                 :omusrmsg:*   # 紧急消息广播所有用户

安全产品常用配置

1. 按 facility 分流,保留原始格式

apache
# /etc/rsyslog.conf
auth,authpriv.*         /var/log/auth.log
kern.*                  /var/log/kern.log
daemon.*                /var/log/daemon.log
*.emerg                 :omusrmsg:*

2. 模板配置:兼容 RFC 5424(高精度时间戳)

apache
# 高精度时间戳模板
template(name="RFC5424" type="string"
         string="%timestamp:::date-rfc3339% %hostname% %syslogtag%%msg%\n")
$ActionFileDefaultTemplate RFC5424

3. 安全采集推荐配置

apache
# 确保日志不丢失
module(load="imuxsock")          # 本地 socket 输入
$SystemLogRateLimitInterval 0    # 关闭限流(生产环境谨慎)

# 远程接收(安全产品作为日志中心时启用)
module(load="imtcp")
input(type="imtcp" port="514")

日志文件的安全含义

文件路径记录的安全事件
/var/log/auth.logSSH 登录成功/失败、sudo 执行、用户创建/删除
/var/log/kern.logiptables 拒绝、SELinux 拒绝、内核漏洞告警
/var/log/syslog综合日志,包含 cron、mail 等服务
/var/log/btmp失败的登录尝试(二进制格式,用 lastb 读取)
/var/log/wtmp用户登录/登出历史(last 命令)
/var/log/secureRedHat/CentOS 系列的认证日志

Golang 基础解析:完整示例

以下是一个从 TCP 读取原始 syslog 并解析的 demo 框架:

go
package main

import (
    "bufio"
    "fmt"
    "net"
    "strconv"
    "strings"
    "time"
)

// SyslogMessage 表示解析后的 syslog 消息
type SyslogMessage struct {
    Facility  int
    Priority  int
    Severity  string
    Timestamp time.Time
    Hostname  string
    Tag       string
    Content   string
    Raw       string
}

// severityMap Priority 值到严重级别字符串的映射
var severityMap = map[int]string{
    0: "emerg", 1: "alert", 2: "crit", 3: "err",
    4: "warning", 5: "notice", 6: "info", 7: "debug",
}

// parseSyslog 解析原始 syslog 消息(RFC 3164 简化版)
func parseSyslog(raw string) (*SyslogMessage, error) {
    if len(raw) < 4 || raw[0] != '<' {
        return nil, fmt.Errorf("invalid syslog format")
    }

    // 提取 PRI
    end := strings.Index(raw, ">")
    if end == -1 {
        return nil, fmt.Errorf("missing PRI terminator")
    }
    pri, _ := strconv.Atoi(raw[1:end])
    facility := pri / 8
    priority := pri % 8

    // 剩余部分
    rest := strings.TrimSpace(raw[end+1:])

    // 简单分割:时间戳 + 主机名 + 消息
    parts := strings.SplitN(rest, " ", 3)
    if len(parts) < 3 {
        return nil, fmt.Errorf("invalid syslog header")
    }

    // 解析时间戳(格式:Mmm dd hh:mm:ss)
    timestampStr := parts[0] + " " + parts[1]
    timestamp, _ := parseRFC3164Time(timestampStr, time.Now().Year())

    // 主机名和消息
    hostname := parts[2]

    return &SyslogMessage{
        Facility:  facility,
        Priority:  priority,
        Severity:  severityMap[priority],
        Timestamp: timestamp,
        Hostname:  hostname,
        Raw:       raw,
    }, nil
}

// parseRFC3164Time 解析 RFC 3164 格式时间戳
func parseRFC3164Time(timestamp string, currentYear int) (time.Time, error) {
    layout := "Jan 2 15:04:05"
    t, err := time.Parse(layout, timestamp)
    if err != nil {
        return time.Time{}, err
    }
    return time.Date(currentYear, t.Month(), t.Day(),
        t.Hour(), t.Minute(), t.Second(), 0, time.Local), nil
}

// handleConnection 处理单个 TCP 连接
func handleConnection(conn net.Conn) {
    defer conn.Close()
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        raw := scanner.Text()
        msg, err := parseSyslog(raw)
        if err != nil {
            fmt.Printf("Parse error: %v\n", err)
            continue
        }
        fmt.Printf("[%s] [%s] Facility=%d Host=%s\n",
            msg.Timestamp.Format("2006-01-02 15:04:05"),
            msg.Severity, msg.Facility, msg.Hostname)
    }
}

func main() {
    ln, err := net.Listen("tcp", ":5514")
    if err != nil {
        panic(err)
    }
    defer ln.Close()

    fmt.Println("🚀 Syslog collector listening on :5514")
    for {
        conn, err := ln.Accept()
        if err != nil {
            continue
        }
        go handleConnection(conn)
    }
}

安全开发者的实战要点

常见解析陷阱

陷阱解决方案
时间戳无年份上下文判断(日志文件写入时间 vs 事件时间)
多行消息(如堆栈跟踪)需要状态机解析,不能按行简单分割
非标准格式(如网络设备)配置设备使用标准格式,或编写适配器
UDP 丢包生产环境使用 TCP 或 RELP 协议

日志采集的合规要求

等保 2.0、GDPR、PCI-DSS 等标准对日志有明确要求:

  • 完整性:日志不能被篡改(考虑签名或安全传输)
  • 可用性:日志不能丢失(TCP + 本地缓存)
  • 机密性:传输加密(TLS,在第二篇详述)

小结

本文从协议底层到配置实战,为安全开发者构建了 syslog 的知识基础:

  • ✅ 理解 PRI = Facility × 8 + Priority,能手动解析和构造
  • ✅ 区分 RFC 3164 / 5424 的时间戳格式差异
  • ✅ 掌握 rsyslog 的核心配置语法和分流策略
  • ✅ 用 Golang 实现基础的 syslog TCP 接收端

下一篇预告:《Syslog 实战篇:Golang 安全产品集成指南》将深入 TLS 证书生成、高性能解析库、对接 SIEM 等生产级话题,并给出完整的可运行 demo。

参考资源

最后更新2026/06/06 06:12
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal