-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathaudit-follow
executable file
·159 lines (138 loc) · 5.25 KB
/
audit-follow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env python3
import itertools as it, operator as op, functools as ft
import os, sys, subprocess, signal, json, re, time
import binascii, struct, socket, atexit
def print_sig(*args, **kws):
try: print(*args, **kws)
except OSError: os.kill(os.getpid(), signal.SIGINT)
def print_audit_props(m):
aid = m.get('_AUDIT_ID')
if not aid:
print_sig(
'No audit props for msg (mono-ts:',
m['__MONOTONIC_TIMESTAMP'], ')' )
return
print_sig('Audit props for', aid)
for k, v in m.items():
k = k.lower()
if not k.startswith('_audit_'): continue
k = k[7:]
print_sig(' ', k, v)
def decode_hex_kv(kv):
k, v = kv.split('=', 1)
if k == 'saddr':
if len(v) >= 16 and v.startswith('0200'):
port, ipaddr = struct.unpack('>HL', binascii.a2b_hex(v[4:16]))
ip_addr = socket.inet_ntoa(struct.pack('>L', ipaddr))
v = '{}:{}'.format(ip_addr, str(port))
else:
try: v = binascii.a2b_hex(v)
except ValueError: pass
else:
try: v = repr(v.decode().replace('\0', ' '))
except UnicodeDecodeError: v = '[{}]'.format(repr(v).lstrip('b'))
return k, v
def main(args=None):
import argparse
parser = argparse.ArgumentParser(
description='Follow and decode audit msgs from journalctl output.')
parser.add_argument('-o', '--journalctl-opts',
action='append', metavar='opts',
help='Additional options/arguments to pass to journalctl.'
' Will be split on spaces, unless option is used multiple times.'
' Example: -o="--since=-1h SYSLOG_IDENTIFIER=audit"')
parser.add_argument('-a', '--all', action='store_true',
help='Do not strip prefixes and misc noise from audit msgs.')
parser.add_argument('-F', '--grep-str', metavar='string',
help='Print only events that contain exact substring that is provided.')
parser.add_argument('-s', '--sep', metavar='char ["x" count]',
help='Separator char and optional'
' count of these to use between multi-line audit msgs.'
' Default: -s=-x80, empty or no arg can be used for empty line.')
parser.add_argument('-r', '--reltime', action='store_true',
help='Print relative monotonic timestamp before every line.')
parser.add_argument('-d', '--difftime', action='store_true',
help='Print timestamp since last logged event on each line.')
parser.add_argument('-i', '--stdin', action='store_true',
help='Read JSON lines from stdin instead of running "journalctl -af -o json".')
parser.add_argument('-l', '--line-len',
type=int, metavar='n', default=95,
help='Max length of individual lines in the output.'
' Longer ones will be split. Default: %(default)s')
opts = parser.parse_args(sys.argv[1:] if args is None else args)
journalctl_opts = opts.journalctl_opts or list()
if len(journalctl_opts) == 1: journalctl_opts = journalctl_opts[0].strip().split()
sys.stdout = open(sys.stdout.fileno(), 'w', 1)
signal.signal(signal.SIGINT, signal.SIG_DFL)
if not opts.stdin:
proc = subprocess.Popen(
['journalctl', '-af', '-o', 'json'] + journalctl_opts,
stdout=subprocess.PIPE )
atexit.register(proc.terminate)
stream = proc.stdout
else: stream = sys.stdin
sep = '-' if opts.sep is None else opts.sep.strip()
if len(sep) > 1:
sep, sep_c = sep.split('x', 1)
sep, sep_c = sep.strip(), int(sep_c)
else: sep_c = min(80, opts.line_len)
if sep: sep *= sep_c
msg_buff_use = False
if opts.grep_str: msg_buff_use = True
line_proc = op.itemgetter(
'__MONOTONIC_TIMESTAMP',
'SYSLOG_FACILITY', 'SYSLOG_IDENTIFIER', 'MESSAGE' )
msg_buff = list()
ts0 = ts_prev = audit_id_prev = None
for line in stream:
if isinstance(line, bytes): line = line.decode(errors='replace')
line = json.loads(line)
ts, log_fac, log_id, msg = line_proc(line)
ts = int(ts) / 1000
if ts0 is None: ts0 = ts_prev = ts
if log_fac=='0' and log_id=='kernel' and msg.startswith('audit:'):
audit_id = re.search(r'audit\([\d.]+:(\d+)\)', msg)
if audit_id: audit_id = int(audit_id.group(1))
elif log_fac=='4' and log_id=='audit' and '_AUDIT_ID' in line:
audit_id = line['_AUDIT_ID']
else: continue
if not opts.all:
msg = re.sub(r'^(?:audit: )?type=(\d+) audit\(.*\):\s+', '[\g<1>] ', msg)
msg_list = list()
for m in re.finditer(
r'[\w\d]+=[A-F0-9]{8}[A-F0-9]+', msg.strip() ):
a, b = m.span()
k, v = decode_hex_kv(m.group())
msg_list.extend(msg[:a].split())
msg_list.append('{}={}'.format(k, v))
msg = msg[b:]
if msg: msg_list.extend(msg.split())
if sep and audit_id != audit_id_prev:
if msg_buff:
msg_buff_str = '\n'.join(msg_buff)
if opts.grep_str:
if opts.grep_str not in msg_buff_str: msg_buff_str = ''
if msg_buff_str: print_sig(msg_buff_str)
msg_buff.clear()
if audit_id_prev and (not msg_buff_use or msg_buff_str): print_sig(sep)
audit_id_prev = audit_id
prefix = ''
if opts.reltime: ts_prev, prefix = ts, '[{:>7.3f}] '.format(ts - ts_prev)
if opts.difftime: prefix = '[{:>9.3f}] '.format(ts - ts0)
if len(msg_list) <= 2:
msg_buff.append(prefix + ' '.join(msg_list))
continue
while msg_list:
msg = msg_list[0]
for n, part in enumerate(msg_list[1:], 1):
if len(msg) + len(part) < opts.line_len:
msg += ' ' + part
else:
msg_buff.append(prefix + msg)
prefix, msg_list = ' '*4, msg_list[n:]
break
else: break
if not msg_buff_use:
for line in msg_buff: print_sig(line)
msg_buff.clear()
if __name__ == '__main__': sys.exit(main())