rec 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/CHANGELOG +11 -0
  2. data/EXAMPLES +120 -122
  3. data/LICENSE +20 -0
  4. data/README +153 -145
  5. data/lib/rec.rb +5 -1
  6. data/lib/rec/mock-notify.rb +1 -2
  7. data/lib/string.rb +1 -0
  8. metadata +9 -6
data/CHANGELOG ADDED
@@ -0,0 +1,11 @@
1
+ Version 1.0.7
2
+ Simplified rdoc markup for the benefit of yard.
3
+
4
+ Version 1.0.6
5
+ Restructured for better compatibility with gem standards
6
+
7
+ Version 1.0.4
8
+ Added examples
9
+
10
+ Version 1.0.1
11
+ Initial version
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
- # 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
- }
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
- }) { |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
- }
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
- }) { |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
- }
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
- }) { |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
- }
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
- 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
- })
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
- # 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
- }
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
- }) { |state|
114
- state.generate_first_only()
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
- # 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
- }
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
- 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
- }
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
- 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
- }
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
- 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
- }
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
- 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
- })
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
- 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
- }
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
- 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
- })
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
- 1. Install the gem
6
- $ sudo gem install rec
5
+ - Install the gem
7
6
 
8
- 2. Select a ruleset or create your own
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
- 3. Start it up
16
- $ rulesets/rules.rb < /var/log/mail.log 3>missed.log 2>control.log > newevents.log
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
- [Save time]
23
- If you are lazy enough to not want to review all of your log files manually forever, and
24
- smart enough to work out what needs monitoring and when you might want to pay attention,
25
- then wouldn't it be good if you could define those rules and let the computer do what it
26
- does best?
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
- [Generate meaning]
29
- The logs of many applications are filled with entries that are quite low level - perhaps
30
- wonderful for debugging, but typically not terribly meaningful in terms of business.
31
- Wouldn't it be good if we could summarise a bunch of low level events into a single
32
- business event - and then just read the <em>business log</em>.
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
- # log entry => "nfs: server earth not responding"
51
- pattern => /nfs\: server (\w+) not responding/
52
- details => ['host']
53
- # values of interest are captured into a hash => {'host' => 'earth' }
54
- :message => "Server %host$s is down"
55
- # interpolation with named parameters => "Server earth is down"
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
- # For better security, move the next few lines into a file readable only by
73
- # the user running this script eg. /home/rec/alert.conf
74
- # and then require that file
75
- Notify.smtp_credentials("rec@gmail.com", "recret", "myfirm.com")
76
- Notify.emailTo = "me@myfirm.com"
77
- Notify.jabber_credentials("rec@gmail.com", "recret")
78
- Notify.jabberTo = "me@myfirm.com"
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
- state.alert_first_only() # => generate a new event on first original event
84
- # or
85
- Notify.normal(state.alert_first_only()) # => log and also send the new event via email
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
- Sep 16 07:09:22 earth sudo: richard : 3 incorrect password attempts ;...
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
- # single threshold rule
99
- Rule.new(10034, {
100
- :pattern => /\w+ sudo\: (\w+) \: 3 incorrect password attempts/,
101
- :details => ["userid"],
102
- :message => "Failed sudo password for user %userid$s",
103
- :lifespan => 60,
104
- :alert => "'Too much sudo activity' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
105
- :threshold => 3,
106
- :capture => true
107
- }) { |state|
108
- if state.count == state.threshold
109
- Notify.urgent(state.generate_alert())
110
- state.release()
111
- end
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
- [Rule ID]
116
- Each rule must have a unique integer ID (+rid+).
117
- It is the first argument and is mandatory.
118
- Its probably a good idea to 'reserve' a number range for a ruleset
119
- to keep them separate from other rules (eg. 17801-17899 for Postfix-related rules).
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
- [pattern]
123
- The +pattern+ is a regexp designed to match certain log messages.
124
- A +message+ is what's left of a log entry after we have removed the timestamp and
125
- any priority level. For example:
126
- [Thu Aug 16 16:11:21 2012] [error] ap_proxy_connect_backend disabling worker for (127.0.0.1)
127
- # timestamp parsed => 2012-08-16T16:11:21+10:00
128
- # priority ignored => "error"
129
- # message => "ap_proxy_connect_backend disabling worker for (127.0.0.1)"
130
-
131
- [details]
132
- The pattern may contain regexp 'captures' (eg. (\d+.\d+.\d+.\d+) to capture the ip).
133
- For each capture a name should be specified in the +details+ array.
134
- The sequence of captures is as specified for ruby Regexps.
135
- :pattern => /\w+ sudo\: (\w+) \: (\d) incorrect password attempts/,
136
- :details => ["userid", "failures"],
137
- The names chosen for captured values are used as keys to store the values in the same
138
- hash that stores the parameters, so do *not* choose words like +pattern+, +details+,
139
- +message+, +threshold+, +lifespan+, +alert+, +capture+, +continue+, or +action+.
140
-
141
- [message]
142
- The +message+ is a string template into which the captured values are interpolated
143
- to produce a unique key for a state.
144
- :details => ["userid"],
145
- :message => "Failed sudo password for user %userid$s",
146
- # userid = "richard" => "Failed sudo password for user richard"
147
- Note the modified +sprintf+ syntax: the value of +userid+ is inserted into the message
148
- as a string by the String::sprinth method. This becomes the +title+ and key for the state
149
- created by this rule.
150
-
151
- [lifespan]
152
- When a rule creates a state, we need to know how long to remember the state for, and
153
- when to expire it. The +lifespan+ specifies that duration in seconds.
154
-
155
- It is also possible to extend the life of a state should other events take place (with
156
- State::live_another) in the same way that a web session may be extended for another 10
157
- minutes longer at each request.
158
-
159
- [alert]
160
- This is a string template used to generate an output log message (the timestamp will be
161
- prefixed automatically to complete the log entry).
162
- :alert => "'Too much sudo activity' userid=%userid$s attempts=%count$d dur=%dur$0.3fs ",
163
- By convention, we make out log messages very easy to parse by creating name=value pairs,
164
- and single-quoting strings containing spaces, in case the output will be processed further.
165
-
166
- If no +alert+ is provided, it will default to +message+.
167
-
168
- [capture]
169
- The +capture+ parameters tells REC to store the original log entries in the state (in the
170
- +logs+ attribute). You could in this way for example extract a transcript of each web session from
171
- a noisy access log, and output them as each session finishes or expires.
172
- :capture => true
173
-
174
- [threshold]
175
- This parameter is used in the action.
176
- :threshold => 3
177
-
178
- [allstates]
179
- An array of templates used to determine if matching states exist. All the mentioned
180
- states must be found or the rule will not take any action.
181
-
182
- [anystates]
183
- An array of templates used to determine if matching states exist. If any one of the
184
- mentioned states exist, then the rule will execute its action.
185
-
186
- [notstates]
187
- An array of templates used to determine if matching states exist. If any one of the
188
- mentioned states does exist, then the rule will *not* execute its action.
189
-
190
- [Any arbitrary parameter]
191
- Any arbitrary parameters may be added to the rule, and they are passed on to the
192
- state in the +params+ hash.
193
-
194
- The third argument is a block.
195
- [action]
196
- The action is a block with a single argument which is the state created by the rule.
197
- The +count+ of matched events is maintained automatically. In this case, when we have
198
- seen 3 events, we generate an output log entry and also send it by IM, then release
199
- the state (forget about it).
200
- :threshold => 3
201
- }) { |state|
202
- if state.count == state.threshold
203
- Notify.urgent(state.generate_alert())
204
- state.release()
205
- end
206
- }
207
- By the magic of Ruby's #method_missing method (Yes, I'm looking at you Java!) we can
208
- refer to any parameter succinctly instead of a cumbersome hash notation, so:
209
- state.threshold === state.params['threshold']
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
@@ -6,7 +6,11 @@ require 'rec/notify'
6
6
  # - REC::Correlator
7
7
  # - REC::Rule
8
8
  # - REC::State
9
- # - REC::Alert
9
+ # - REC::Notify
10
10
  #
11
11
  module REC
12
12
  end
13
+
14
+ # necessary to remove spurious Object class from rdoc
15
+ class Object # :nodoc:
16
+ end
@@ -1,8 +1,7 @@
1
1
  module REC # :nodoc:
2
2
 
3
3
  # mock the Notify class for testing purposes
4
- # :nodoc:
5
- class Notify
4
+ class Notify # :nodoc:
6
5
 
7
6
  @@emailsSent = []
8
7
  @@jabbersSent = []
data/lib/string.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # Custom extension to String
2
2
  class String
3
+
3
4
  # Interpolates hash values into a formatted string.
4
5
  # s = "Stats uid %uid$-5d belongs to %userid$s"
5
6
  # h = {'uid': 501, 'userid': 'richard}
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
- - 6
10
- version: 1.0.6
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-17 00:00:00 Z
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: rec
84
+ rubyforge_project:
82
85
  rubygems_version: 1.8.24
83
86
  signing_key:
84
87
  specification_version: 3