rec 1.0.6 → 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +11 -0
- data/EXAMPLES +120 -122
- data/LICENSE +20 -0
- data/README +153 -145
- data/lib/rec.rb +5 -1
- data/lib/rec/mock-notify.rb +1 -2
- data/lib/string.rb +1 -0
- metadata +9 -6
data/CHANGELOG
ADDED
data/EXAMPLES
CHANGED
@@ -8,30 +8,30 @@ so they employ similar names for easy comparison.
|
|
8
8
|
We are monitoring events where a user has had 3 incorrect password attempts.
|
9
9
|
If we see that happen 3 times (+threshold+) within a minute (+lifespan+), alert the administrator.
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
11
|
+
# single threshold rule
|
12
|
+
Rule.new(10034, {
|
13
|
+
:pattern => %r\w+ sudo\: (\w+) \: 3 incorrect password attempts/,
|
14
|
+
:details => ["userid"],
|
15
|
+
:message => "Failed sudo password for user %userid$s",
|
16
|
+
:lifespan => 60,
|
17
|
+
:alert => "'Too much sudo activity' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
|
18
|
+
:threshold => 3
|
19
|
+
}) { |state|
|
20
|
+
if state.count == state.threshold
|
21
|
+
Notify.urgent(state.generate_alert())
|
22
|
+
state.release()
|
23
|
+
end
|
24
|
+
}
|
25
25
|
|
26
26
|
When we see the first event, a state is created with title "Failed sudo password for user richard".
|
27
27
|
The second event has not effect, beyond automatically incrementing the count.
|
28
28
|
When we see the third event, an output message is generated and logged, and then the generated
|
29
29
|
message is also sent via IM to the administrator. Alternatively:
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
}) { |state|
|
32
|
+
message = state.generate_alert() # writes out a new log entry, and returns it
|
33
|
+
Notify.urgent(message) # sends the message to the administrator
|
34
|
+
}
|
35
35
|
|
36
36
|
Finally, the state is released (we forget all about it).
|
37
37
|
|
@@ -40,21 +40,21 @@ would start counting again. Suppose we wanted to avoid that, and just keep on ig
|
|
40
40
|
more events in a sliding window until the user has given it a 3 minute rest.
|
41
41
|
The action could be modified in this way:
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
}) { |state|
|
44
|
+
Notify.urgent(state.generate_alert()) if state.count == state.threshold
|
45
|
+
# keep on pushing expiry out to 3 minutes after the last event
|
46
|
+
state.extend_for(180) if state.count >= state.threshold
|
47
|
+
}
|
48
48
|
|
49
49
|
Suppose we want to check for 3 events within 60 seconds, and then ignore further events
|
50
50
|
for a fixed 5 minutes.
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
52
|
+
}) { |state|
|
53
|
+
if state.count == state.threshold
|
54
|
+
Notify.urgent(state.generate_alert())
|
55
|
+
state.extend_for(300) # expire exactly 5 minutes after the 3rd event
|
56
|
+
end
|
57
|
+
}
|
58
58
|
|
59
59
|
== Adding a final block
|
60
60
|
If we want to see one message when the user first has trouble, then another message
|
@@ -64,24 +64,24 @@ given is previous examples is stored in the +params+ as +action+.
|
|
64
64
|
Instead, the +action+ block may be specified directly as a member of the params hash,
|
65
65
|
and the +onexpiry+ must be specified in this way if it is to be used.
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
67
|
+
Rule.new(10034, {
|
68
|
+
:pattern => /^\s+\w+\s+sudo\[\d+\]\:\s+(\w+) \:/,
|
69
|
+
:details => ["userid"],
|
70
|
+
:message => "sudo activity for user %userid$s",
|
71
|
+
:threshold => 3,
|
72
|
+
:lifespan => 60,
|
73
|
+
:alert => "'Too much sudo activity' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
|
74
|
+
:expiry => "'Gave sudo a rest' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
|
75
|
+
:action => Proc.new { |state|
|
76
|
+
if state.count == state.threshold
|
77
|
+
Notify.urgent(state.generate(:alert))
|
78
|
+
state.release()
|
79
|
+
end
|
80
|
+
},
|
81
|
+
:final => Proc.new { |state|
|
82
|
+
Notify.normal(state.generate(:expiry))
|
83
|
+
}
|
84
|
+
})
|
85
85
|
|
86
86
|
When the state is about to expire, its :onexpiry block will be called. In this case,
|
87
87
|
it generates a log entry using the :final message template, and sends the message
|
@@ -91,16 +91,16 @@ to the administrator via normal (email) delivery.
|
|
91
91
|
Compression involves converting a stream of events into fewer, preferably one. In this example,
|
92
92
|
we report when a skype conversation starts and then suppress all further noise for about 8 minutes.
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
94
|
+
# suppression rule
|
95
|
+
Rule.new(10035, {
|
96
|
+
:pattern => /^\s\w+\sFirewall\[\d+\]\:\sSkype is listening from 0.0.0.0:(\d+)/,
|
97
|
+
:details => ["port"],
|
98
|
+
:message => "Skype conversation started on port %port$d",
|
99
|
+
:alert => "Skype running on port %port$d",
|
100
|
+
:lifespan => 479
|
101
|
+
}) { |state|
|
102
|
+
state.generate_first_only(:alert)
|
103
|
+
}
|
104
104
|
|
105
105
|
The <code>generate_first_only</code> method creates a new event using the :alert template
|
106
106
|
only if the state's +count+ is 1, so it notices the first event and ignores all subsequent
|
@@ -110,24 +110,24 @@ By default, generate() and generate_first_only() use the :alert template.
|
|
110
110
|
If no :alert was provided, the :message will be used instead. In this example,
|
111
111
|
we could have omitted the argument:
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
}) { |state|
|
114
|
+
state.generate_first_only()
|
115
|
+
}
|
116
116
|
|
117
117
|
== Pairs of rules
|
118
118
|
We want to know when a server goes down, and when it comes back up again.
|
119
119
|
In this example, rule 10036 creates a new log entry when we first
|
120
120
|
see the server is not responding, and the state persists for 5 minutes.
|
121
121
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
122
|
+
# pair rule
|
123
|
+
Rule.new(10036, {
|
124
|
+
:pattern => /^\s\w+\s\w+\: nfs\: server (\w+) not responding/,
|
125
|
+
:details => ["host"],
|
126
|
+
:message => "Server %host$s is down",
|
127
|
+
:lifespan => 300
|
128
|
+
}) { |state|
|
129
|
+
state.generate_first_only()
|
130
|
+
}
|
131
131
|
|
132
132
|
Rule 10037 looks for a message saying the server is OK, *AND* that there is a state
|
133
133
|
with a title like "Server earth is down". The :allstates parameter contains an array of
|
@@ -136,16 +136,16 @@ templates - the rule does not react to the event unless all of the named states
|
|
136
136
|
When all the conditions are satisfied, the rule generates a new log entry that
|
137
137
|
the server is up, and then forget both states.
|
138
138
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
139
|
+
Rule.new(10037, {
|
140
|
+
:pattern => /^\s\w+\s\w+\: nfs\: server (\w+) OK/,
|
141
|
+
:details => ["host"],
|
142
|
+
:message => "Server %host$s is up again",
|
143
|
+
:allstates => ["Server %host$s is down"]
|
144
|
+
}) {|state|
|
145
|
+
state.generate()
|
146
|
+
state.release("Server %host$s is down")
|
147
|
+
state.release()
|
148
|
+
}
|
149
149
|
|
150
150
|
Since no :alert is specified, it defaults to the :message. So +generate+ will
|
151
151
|
log a message that "Server earth is up again".
|
@@ -158,18 +158,18 @@ Now suppose we want to know how long the server was down. We have two options:
|
|
158
158
|
|
159
159
|
Since we've already seen how to add a final block, lets take option 2.
|
160
160
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
161
|
+
Rule.new(10037, {
|
162
|
+
:pattern => /^\s\w+\s\w+\: nfs\: server (\w+) OK/,
|
163
|
+
:details => ["host"],
|
164
|
+
:message => "Server %host$s is up again after %outage$d minutes",
|
165
|
+
:allstates => ["Server %host$s is down"]
|
166
|
+
}) {|state|
|
167
|
+
duration = State.find("Server %host$s is down", state).age()
|
168
|
+
state.params[:outage] = (duration/60).to_i()
|
169
|
+
state.generate()
|
170
|
+
state.release("Server %host$s is down")
|
171
|
+
state.release()
|
172
|
+
}
|
173
173
|
|
174
174
|
We can obtain the duration of the outage with the State#find method, which
|
175
175
|
interpolates the current state's values into the template, and finds the
|
@@ -184,43 +184,41 @@ Having calculated the duration, we generate the message, and forget both states.
|
|
184
184
|
Several actions are so common they have been provided as constants to make the rules
|
185
185
|
more succinct but still readable. One is to generate a message on the first event only:
|
186
186
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
187
|
+
Rule.new(10036, {
|
188
|
+
:pattern => /^\s\w+\s\w+\: nfs\: server (\w+) not responding/,
|
189
|
+
:details => ["host"],
|
190
|
+
:message => "Server %host$s is down",
|
191
|
+
:lifespan => 300
|
192
|
+
}) { |state|
|
193
|
+
state.generate_first_only()
|
194
|
+
}
|
195
195
|
|
196
196
|
can be abbreviated in this way:
|
197
197
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
198
|
+
Rule.new(10036, {
|
199
|
+
:pattern => /^\s\w+\s\w+\: nfs\: server (\w+) not responding/,
|
200
|
+
:details => ["host"],
|
201
|
+
:message => "Server %host$s is down",
|
202
|
+
:lifespan => 300,
|
203
|
+
:action => State::Generate_first_only
|
204
|
+
})
|
205
205
|
|
206
206
|
Another common action is to generate a message and release the state immediately:
|
207
207
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
208
|
+
Rule.new(10040, {
|
209
|
+
:pattern => /Accepted password for (\w+) from (\d+\.\d+\.\d+\.\d+)/,
|
210
|
+
:details => ["user", "ip"],
|
211
|
+
:message => "User %user$s signed in via SSH from %ip$s",
|
212
|
+
}) { |state|
|
213
|
+
state.generate()
|
214
|
+
state.release()
|
215
|
+
}
|
216
216
|
|
217
217
|
can be abbreviated in this way:
|
218
218
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
219
|
+
Rule.new(10040, {
|
220
|
+
:pattern => /Accepted password for (\w+) from (\d+\.\d+\.\d+\.\d+)/,
|
221
|
+
:details => ["user", "ip"],
|
222
|
+
:message => "User %user$s signed in via SSH from %ip$s",
|
223
|
+
:action => State::Generate_and_release
|
224
|
+
})
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Richard Kernahan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
CHANGED
@@ -2,40 +2,44 @@
|
|
2
2
|
Correlates events in order to generate a smaller set of more meaningful events.
|
3
3
|
|
4
4
|
== Installation
|
5
|
-
|
6
|
-
$ sudo gem install rec
|
5
|
+
- Install the gem
|
7
6
|
|
8
|
-
|
9
|
-
#!/usr/bin/ruby
|
10
|
-
require 'rec'
|
11
|
-
include REC
|
12
|
-
require 'rulesets/postfix-rules'
|
13
|
-
Correlator::start()
|
7
|
+
$ sudo gem install rec
|
14
8
|
|
15
|
-
|
16
|
-
|
9
|
+
- Select a ruleset or create your own
|
10
|
+
|
11
|
+
require 'rec'
|
12
|
+
include REC
|
13
|
+
require 'rulesets/postfix-rules'
|
14
|
+
Correlator::start()
|
15
|
+
|
16
|
+
- Start it up
|
17
|
+
|
18
|
+
$ rulesets/rules.rb < /var/log/mail.log 3>missed.log 2>control.log > newevents.log
|
17
19
|
|
18
20
|
== Why correlate events?
|
19
21
|
We all know that we should read our log files. But reading log files is *really* boring,
|
20
22
|
and frankly its easy to miss important things in all the superfluous detail.
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
=== Save time
|
25
|
+
If you are lazy enough to not want to review all of your log files manually forever, and
|
26
|
+
smart enough to work out what needs monitoring and when you might want to pay attention,
|
27
|
+
then wouldn't it be good if you could define those rules and let the computer do what it
|
28
|
+
does best?
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
=== Generate meaning
|
31
|
+
The logs of many applications are filled with entries that are quite low level - perhaps
|
32
|
+
wonderful for debugging, but typically not terribly meaningful in terms of business.
|
33
|
+
Wouldn't it be good if we could summarise a bunch of low level events into a single
|
34
|
+
business event - and then just read the <em>business log</em>.
|
33
35
|
|
34
36
|
== Alternatives
|
35
37
|
There are several alternatives to REC which may suit your needs better:
|
38
|
+
|
36
39
|
* splunk[www.splunk.com]
|
37
40
|
* nagios[www.nagios.com]
|
38
41
|
* scalextreme.com[www.scalextreme.com]
|
42
|
+
|
39
43
|
While I like these options, I find they take a lot of configuring.
|
40
44
|
They also has some dependencies that make them a bit heavier than you may want.
|
41
45
|
If you just want to keep track of a few kinds of events, want a lot of flexibility
|
@@ -47,12 +51,12 @@ The Correlator reads the events, and attempts to match an event against each Rul
|
|
47
51
|
If an event matches a rule, the rule creates a State which just means we're remembering
|
48
52
|
that the event matched a rule. The pattern to match is a regexp, and the captured values
|
49
53
|
are named. For example
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
# log entry => "nfs: server earth not responding"
|
55
|
+
pattern => /nfs\: server (\w+) not responding/
|
56
|
+
details => ['host']
|
57
|
+
# values of interest are captured into a hash => {'host' => 'earth' }
|
58
|
+
:message => "Server %host$s is down"
|
59
|
+
# interpolation with named parameters => "Server earth is down"
|
56
60
|
|
57
61
|
A state has a fixed lifetime, set when it is created. At the end of its life, it may simply
|
58
62
|
expire quietly, or a pre-defined action may be executed. For example, if we find a server is down,
|
@@ -69,20 +73,20 @@ distracting alerts.
|
|
69
73
|
|
70
74
|
A notifcation can be sent by email or IM, depending on your preferences and working hours.
|
71
75
|
The destinations and credentials are supplied to your ruleset:
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
# For better security, move the next few lines into a file readable only by
|
77
|
+
# the user running this script eg. /home/rec/alert.conf
|
78
|
+
# and then require that file
|
79
|
+
Notify.smtp_credentials("rec@gmail.com", "recret", "myfirm.com")
|
80
|
+
Notify.emailTo = "me@myfirm.com"
|
81
|
+
Notify.jabber_credentials("rec@gmail.com", "recret")
|
82
|
+
Notify.jabberTo = "me@myfirm.com"
|
79
83
|
|
80
84
|
Rules can then send an alert when desired. Two common cases involve alerting immediately
|
81
85
|
on the first event (eg. "host terra is down"), and alerting on expiry or at a subsequent event
|
82
86
|
(eg. "host terra is back up").
|
83
|
-
|
84
|
-
|
85
|
-
|
87
|
+
state.alert_first_only() # => generate a new event on first original event
|
88
|
+
# or
|
89
|
+
Notify.normal(state.alert_first_only()) # => log and also send the new event via email
|
86
90
|
|
87
91
|
In most cases, however, it is not necessary to alert the administrator at all. It is enough to
|
88
92
|
log the new event in the output logfile for later review.
|
@@ -91,121 +95,125 @@ log the new event in the output logfile for later review.
|
|
91
95
|
Warn if an user is having trouble executing sudo commands
|
92
96
|
The log entry (/var/log/secure) looks like this:
|
93
97
|
|
94
|
-
|
98
|
+
Sep 16 07:09:22 earth sudo: richard : 3 incorrect password attempts ;...
|
95
99
|
|
96
100
|
and the rule might look like this:
|
97
101
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
102
|
+
# single threshold rule
|
103
|
+
Rule.new(10034, {
|
104
|
+
:pattern => /\w+ sudo\: (\w+) \: 3 incorrect password attempts/,
|
105
|
+
:details => ["userid"],
|
106
|
+
:message => "Failed sudo password for user %userid$s",
|
107
|
+
:lifespan => 60,
|
108
|
+
:alert => "'Too much sudo activity' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
|
109
|
+
:threshold => 3,
|
110
|
+
:capture => true
|
111
|
+
}) { |state|
|
112
|
+
if state.count == state.threshold
|
113
|
+
Notify.urgent(state.generate_alert())
|
114
|
+
state.release()
|
115
|
+
end
|
116
|
+
}
|
113
117
|
|
114
118
|
Let's look at each part:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
119
|
+
|
120
|
+
=== rule ID
|
121
|
+
Each rule must have a unique integer ID (+rid+).
|
122
|
+
It is the first argument and is mandatory.
|
123
|
+
Its probably a good idea to 'reserve' a number range for a ruleset
|
124
|
+
to keep them separate from other rules (eg. 17801-17899 for Postfix-related rules).
|
120
125
|
|
121
126
|
The second argument is a hash of options:
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
127
|
+
=== pattern
|
128
|
+
The +pattern+ is a regexp designed to match certain log messages.
|
129
|
+
A +message+ is what's left of a log entry after we have removed the timestamp and
|
130
|
+
any priority level. For example:
|
131
|
+
[Thu Aug 16 16:11:21 2012] [error] ap_proxy_connect_backend disabling worker for (127.0.0.1)
|
132
|
+
# timestamp parsed => 2012-08-16T16:11:21+10:00
|
133
|
+
# priority ignored => "error"
|
134
|
+
# message => "ap_proxy_connect_backend disabling worker for (127.0.0.1)"
|
135
|
+
|
136
|
+
=== details
|
137
|
+
The pattern may contain regexp 'captures' (eg. (\d+.\d+.\d+.\d+) to capture the ip).
|
138
|
+
For each capture a name should be specified in the +details+ array.
|
139
|
+
The sequence of captures is as specified for ruby Regexps.
|
140
|
+
:pattern => /\w+ sudo\: (\w+) \: (\d) incorrect password attempts/,
|
141
|
+
:details => ["userid", "failures"],
|
142
|
+
The names chosen for captured values are used as keys to store the values in the same
|
143
|
+
hash that stores the parameters, so do *not* choose words like +pattern+, +details+,
|
144
|
+
+message+, +threshold+, +lifespan+, +alert+, +capture+, +continue+, or +action+.
|
145
|
+
|
146
|
+
=== message
|
147
|
+
The +message+ is a string template into which the captured values are interpolated
|
148
|
+
to produce a unique key for a state.
|
149
|
+
:details => ["userid"],
|
150
|
+
:message => "Failed sudo password for user %userid$s",
|
151
|
+
# userid = "richard" => "Failed sudo password for user richard"
|
152
|
+
Note the modified +sprintf+ syntax: the value of +userid+ is inserted into the message
|
153
|
+
as a string by the String::sprinth method. This becomes the +title+ and key for the state
|
154
|
+
created by this rule.
|
155
|
+
|
156
|
+
=== lifespan
|
157
|
+
When a rule creates a state, we need to know how long to remember the state for, and
|
158
|
+
when to expire it. The +lifespan+ specifies that duration in seconds.
|
159
|
+
|
160
|
+
It is also possible to extend the life of a state should other events take place (with
|
161
|
+
State::live_another) in the same way that a web session may be extended for another 10
|
162
|
+
minutes longer at each request.
|
163
|
+
|
164
|
+
=== alert
|
165
|
+
This is a string template used to generate an output log message (the timestamp will be
|
166
|
+
prefixed automatically to complete the log entry).
|
167
|
+
:alert => "'Too much sudo activity' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
|
168
|
+
By convention, we make out log messages very easy to parse by creating name=value pairs,
|
169
|
+
and single-quoting strings containing spaces, in case the output will be processed further.
|
170
|
+
|
171
|
+
If no +alert+ is provided, it will default to +message+.
|
172
|
+
|
173
|
+
=== capture
|
174
|
+
The +capture+ parameters tells REC to store the original log entries in the state (in the
|
175
|
+
+logs+ attribute). You could in this way for example extract a transcript of each web session from
|
176
|
+
a noisy access log, and output them as each session finishes or expires.
|
177
|
+
:capture => true
|
178
|
+
|
179
|
+
=== threshold
|
180
|
+
This parameter is used in the action.
|
181
|
+
:threshold => 3
|
182
|
+
|
183
|
+
=== allstates
|
184
|
+
An array of templates used to determine if matching states exist. All the mentioned
|
185
|
+
states must be found or the rule will not take any action.
|
186
|
+
|
187
|
+
=== anystates
|
188
|
+
An array of templates used to determine if matching states exist. If any one of the
|
189
|
+
mentioned states exist, then the rule will execute its action.
|
190
|
+
|
191
|
+
===notstates
|
192
|
+
An array of templates used to determine if matching states exist. If any one of the
|
193
|
+
mentioned states does exist, then the rule will *not* execute its action.
|
194
|
+
|
195
|
+
=== Any arbitrary parameter
|
196
|
+
Any arbitrary parameters may be added to the rule, and they are passed on to the
|
197
|
+
state in the +params+ hash.
|
198
|
+
|
199
|
+
=== action
|
200
|
+
The action is a block with a single argument which is the state created by the rule.
|
201
|
+
The +count+ of matched events is maintained automatically. In this case, when we have
|
202
|
+
seen 3 events, we generate an output log entry and also send it by IM, then release
|
203
|
+
the state (forget about it).
|
204
|
+
|
205
|
+
Rule.new(10045, {
|
206
|
+
#...
|
207
|
+
:threshold => 3
|
208
|
+
}) { |state|
|
209
|
+
if state.count == state.threshold
|
210
|
+
Notify.urgent(state.generate_alert())
|
211
|
+
state.release()
|
212
|
+
end
|
213
|
+
}
|
214
|
+
|
215
|
+
By the magic of Ruby's #method_missing method (Yes, I'm looking at you Java!) we can
|
216
|
+
refer to any parameter succinctly instead of a cumbersome hash notation, so:
|
217
|
+
state.threshold === state.params['threshold']
|
210
218
|
|
211
219
|
For more examples, see the EXAMPLES page.
|
data/lib/rec.rb
CHANGED
data/lib/rec/mock-notify.rb
CHANGED
data/lib/string.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 7
|
10
|
+
version: 1.0.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Richard Kernahan
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-09-
|
18
|
+
date: 2012-09-18 00:00:00 Z
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description: "\t\tSifts through your log files in real time, using stateful intelligence to determine\n\
|
@@ -33,6 +33,8 @@ extensions: []
|
|
33
33
|
extra_rdoc_files:
|
34
34
|
- README
|
35
35
|
- EXAMPLES
|
36
|
+
- LICENSE
|
37
|
+
- CHANGELOG
|
36
38
|
files:
|
37
39
|
- lib/rec.rb
|
38
40
|
- lib/rec/rule.rb
|
@@ -46,12 +48,13 @@ files:
|
|
46
48
|
- rulesets/postfix-rules.rb
|
47
49
|
- README
|
48
50
|
- EXAMPLES
|
51
|
+
- LICENSE
|
52
|
+
- CHANGELOG
|
49
53
|
homepage: http://rubygems.org/gems/rec
|
50
54
|
licenses: []
|
51
55
|
|
52
56
|
post_install_message:
|
53
57
|
rdoc_options:
|
54
|
-
- --show-hash
|
55
58
|
- --main
|
56
59
|
- README
|
57
60
|
- --title
|
@@ -78,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
81
|
version: "0"
|
79
82
|
requirements: []
|
80
83
|
|
81
|
-
rubyforge_project:
|
84
|
+
rubyforge_project:
|
82
85
|
rubygems_version: 1.8.24
|
83
86
|
signing_key:
|
84
87
|
specification_version: 3
|