在Linux中,当某个进程调用audit_set_pid将自己的pid保存到内核的audit模块后,如果有日志生成,kaudit内核线程就会通过netlink通信机制将审计日志发送给audit_pid,因此,只能有一个进程占用audit并接收audit日志,那如果有另一个进程已经占用audit呢?例如,auditd或者其他组件。
一种方式是可以将选择权交给用户,用户可以看到当前占用audit的进程,然后再决定是否将已经占用audit的进程杀死,然后进行占用。另一种方式是,如果用户已经在使用auditd接收日志,是否有一种方式可以让auditd在运行的同时我们也能够接收和处理日志呢?这种场景下就可以使用audispd。
audispd是作为auditd的子进程运行,而audispd会开启一些插件用于接收审计日志,例如,syslog或者sedispatch等,也就是说,日志转发路径变成了:
kauditd -> auditd -> audispd -> af_unix/syslog/sedispatch
下面逐个看下配置文件。
auditd.conf:
local_events = yes
write_logs = yes // 将日志写入磁盘
log_file = /var/log/audit/audit.log
log_group = root
log_format = RAW
flush = INCREMENTAL_ASYNC // 刷新模式,当前为异步增量刷新模式
freq = 50 // 接收到多少条日志后就写入磁盘
max_log_file = 8 // 日志文件大小,8MB
num_logs = 5 // 最大文件个数
priority_boost = 4 //
disp_qos = lossy
dispatcher = /sbin/audispd // 将审计日志转发的目的进程的文件名
以上是auditd.conf中的部分配置项,除了控制auditd写入日志文件的配置项,还有与日志转发相关的两个配置项:dispatcher,disp_qos,其中,dispatcher是负责日志转发的组件,auditd在启动后会拉起该程序,然后将收到的日志转发给该程序,该程序可以从标准输入读取审计日志,disp_qos则是控制当审计日志太多时auditd和dispatcher之间的行为,auditd和dispatcher之间有一个128K的队列,当队列满时,日志会被丢弃(lossy)或者auditd会等待队列有空间可以放入(lossless)。
audispd.conf:
q_depth = 250 // 队列深度
overflow_action = SYSLOG // 当内部队列满时的行为,当前值表示会写入一条syslog的日志
priority_boost = 4 //
max_restarts = 10 // 重启插件的最大次数
name_format = HOSTNAME // 节点信息如何插入日志中,当前值表示会将HOSTNAME写入到日志中
plugin_dir = /etc/audisp/plugins.d/ // 插件的路径
在plugin_dir目录下存放的就是插件的配置,每个配置项告诉audispd应该如何转发日志,例如,af_unix.conf:
active = no // 是否启用,yes/no
direction = out //
path = builtin_af_unix // 插件二进制的绝对路径,对于内部插件,就是插件的名称,内部插件有af_unix和syslog
type = builtin // 类型,builtin/always
args = 0640 /var/run/audispd_events // 参数
format = string // 格式,binary/string
从插件类型看,总共有三种类型:
内部插件的af_unix:audispd将审计日志发送给unix套接字
内部插件的syslog:audispd将审计日志发送给syslog
外部插件:audispd将审计日志发送给某个程序的标准输入
从上面对几个配置文件进行分析可以发现,auditd和audispd只是作为审计日志转发的中间组件,并且使用队列作为中间缓冲,而对于内核的日志发送者来说,日志的接收者只有auditd。
从几个组件的启动方来看,auditd负责拉起audispd,audispd负责拉起always类型的插件。
因此,如果在某些场景下希望与auditd同时运行,且获取审计日志,可以使用audispd的af_unix插件,但是可能存在其他问题:
如何查看auditd的版本号?auditd自身没有提供查看版本号的选项,只能通过日志或者包管理工具查看auditd的版本号。
CentOS中,在/var/log/messages中会打印auditd的版本号,也可以通过rpm -qi audit
查看版本号。对于ubuntu类,在/var/log/kern.log中没看到auditd的版本号,但是可以通过dpkg -l | grep auditd
查看版本号。
audit-userspace仓库的标签最早只有2.7.4,这个版本已经有audispd,而从3.0.0开始audispd作为auditd的一个线程工作,因此,auditd就包含了audispd原有的能力。
由于使用audispd进行日志中转,在中转过程中引入了两个队列:
测试环境:
只启动auditd,audispd没有配置插件:
是否丢弃日志可以通过查看auditctl -s
中的lost或者/var/log/messages日志文件。
事件量级(eps) | 是否丢弃日志 | 系统CPU |
---|---|---|
1000 | NO | 1.3% |
5000 | NO | 5% |
6000 | NO | 6% |
7000 | NO | 7% |
8000 | YES | 8% |
9000 | YES | 10% |
10000 | YES | 10% |
启动auditd,audispd开启af_unix插件并使用socat命令接收审计日志:
使用socat - UNIX-CONNECT:/var/run/audispd_events
命令接收审计日志,是否丢弃日志可以查看/var/log/messages中的dispatch err (pipe full) event lost
日志。
事件量级(eps) | q_depth | 是否丢弃日志 | 系统CPU | 备注 |
---|---|---|---|---|
500 | 80 | NO | 2% | |
500 | 250 | NO | 2% | |
500 | 5000 | NO | 2% | |
1000 | 80 | YES | 4% | |
1000 | 250 | YES | 4% | |
1000 | 5000 | YES | 4% | 用例执行3分钟开始丢弃日志 |
不启动auditd,使用其他程序占用并接收审计日志:
#include
#include
#include
int main() {
pid_t pid = getpid();
printf("current pid=%d\n", pid);
int fd = audit_open();
int ret = 0;
ret = audit_set_pid(fd, pid, WAIT_YES);
if(ret<0) {
perror("audit set_pid failed: ");
}
ret = audit_set_backlog_limit(fd, 8192);
if(ret<0) {
perror("audit_set_backlog_limit failed: ");
}
struct audit_reply audit_rep;
struct sockaddr_nl local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.nl_family = AF_NETLINK;
do {
fd_set read_mask;
FD_ZERO(&read_mask);
FD_SET(fd, &read_mask);
ret = select(fd + 1, &read_mask, NULL, NULL, NULL);
if(ret < 0 ) {
break;
}
ret = audit_get_reply(fd, &audit_rep,
GET_REPLY_NONBLOCKING, 0);
audit_rep.msg.data[audit_rep.len] = '\0';
printf("recv audit msg: %s\n", audit_rep.msg.data);
} while(1);
audit_close(fd);
}
如果保留上面的代码中的printf语句,事件量级只能到1000eps,2000eps就开始丢弃日志;如果将printf语句注释,事件量级则可以到9000eps。
从上面的测试结果可以得出以下结论:
audispd能够将auditd收到的日志转发出来供多个程序使用,也让其他程序能够得以与auditd同时运行,但是,使用该方式需要解决auditd的启停问题以及audispd带来的性能损耗问题,因此,如果直接使用audit接收日志不会引起其他的问题,建议还是直接使用占用audit的方式。