-
Notifications
You must be signed in to change notification settings - Fork 0
/
api_irule.txt
412 lines (391 loc) · 18.4 KB
/
api_irule.txt
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
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
#Created by F5ers Matt S. & Mike W.
when RULE_INIT {
# Debug flag - set to 0 to disable local logging
set static::debug 1
if {[catch {
# Set sampling rate (1/3 of requests)
# Pre-compile regular expressions for better performance
set static::sampling_rate 3
set static::email_regex {[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}}
set static::ssn_regex {(\d{3}-\d{2}-\d{4})}
set static::cc_regex {\d{13,19}}
set static::json_ssn_regex {"ssn":\s*"(\d{3}-\d{2}-\d{4})"}
set static::json_dob_regex {"dob":\s*"(\d{4}-\d{2}-\d{2})"}
set static::json_email_regex {"email":\s*"([^"]+@[^"]+)"}
set static::dob_regex {(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4}|\d{2}-\d{2}-\d{4}|\d{2}\.\d{2}\.\d{4})}
set static::valid_years_start 1900
set static::valid_years_end 2099
# Luhn check optimization - moved to static context
set static::luhn_check {
set sum 0
set alternate 0
set card_number [string map {" " ""} $card_number]
for {set i [string length $card_number]} {$i > 0} {incr i -1} {
scan [string index $card_number [expr {$i - 1}]] %d digit
if {$alternate} {
set digit [expr {($digit * 2 > 9) ? ($digit * 2 - 9) : ($digit * 2)}]
}
incr sum $digit
set alternate [expr {!$alternate}]
}
expr {$sum % 10 == 0}
}
} err]} {
log local0.error "Error in RULE_INIT: $err"
}
if {$static::debug} {
log local0. "API Discovery iRule initialized with debug logging enabled"
}
}
when CLIENT_ACCEPTED {
if {[catch {
set z1_logging logging-pool-tls
set z1_remoteLogProtocol TCP
set hsl [HSL::open -proto $z1_remoteLogProtocol -pool $z1_logging]
} err]} {
log local0.error "Error in CLIENT_ACCEPTED: $err"
return
}
}
when HTTP_REQUEST {
if {[catch {
set processRequest 0
## Check if count table exists. If not, initalize with 1 to avoid null errors. Auto-reset count after 180 seconds to keep the table small.
if {[table lookup -notouch "request_counter"] < 1} {
table set "request_counter" 1 180 180
} else {
## Table exists. Increment request count.
set current_count [table incr -notouch "request_counter"]
}
# Determine if this request should be processed
if {[expr {[table lookup -notouch "request_counter"] % $static::sampling_rate == 0}]} {
set processRequest 1
if {$static::debug} {
log local0. "Processing request (counter=[table lookup -notouch "request_counter"])"
}
# Initialize variables once
array set z1_request_data {
sensitive_in_payload false
sensitive_in_headers false
sensitive_data_types {}
payload_type "Unknown"
has_auth false
}
if {[catch {
# Store HTTP request data immediately
set z1_request_data(http_uri) [HTTP::uri]
set z1_request_data(http_host) [HTTP::host]
set z1_request_data(http_method) [HTTP::method]
set z1_request_data(http_version) [HTTP::version]
set z1_request_data(req_content_type) [HTTP::header "Content-Type"]
set z1_request_data(virtual_server) [virtual name]
set z1_request_data(clientsslprofile) [PROFILE::clientssl name]
set z1_request_data(http_referrer) [HTTP::header "Referer"]
set z1_request_data(http_user_agent) [HTTP::header "User-Agent"]
set z1_request_data(vip) [IP::local_addr]
set z1_request_data(client_ip) [IP::client_addr]
set z1_request_data(client_port) [TCP::client_port]
} err]} {
log local0.error "Error collecting request data: $err"
}
# Check Authorization header once
if {[HTTP::header "Authorization"] ne ""} {
set z1_request_data(has_auth) true
}
if {[catch {
foreach header_name [HTTP::header names] {
set header_value [HTTP::header $header_name]
# SSN Check with date format exclusion
if {[regexp $static::ssn_regex $header_value -> ssn]} {
scan $ssn {%d-%d-%d} d1 d2 d3
if {$d1 >= 1 && $d1 <= 12} {
if {$static::debug} {
log local0. "Skipping SSN check for date-like pattern in header"
}
} else {
# Validate SSN
if {$d1 >= 1 && $d1 <= 899 && $d1 != 666 &&
$d2 >= 1 && $d2 <= 99 &&
$d3 >= 1 && $d3 <= 9999} {
lappend z1_request_data(sensitive_data_types) "SSN"
set z1_request_data(sensitive_in_headers) true
}
}
}
# Credit card check with optimized Luhn
if {[regexp $static::cc_regex $header_value cc_number]} {
set card_number $cc_number
if {[catch {eval $static::luhn_check} result] && $result} {
lappend z1_request_data(sensitive_data_types) "CreditCard"
set z1_request_data(sensitive_in_headers) true
}
}
# Email check
if {[regexp $static::email_regex $header_value]} {
lappend z1_request_data(sensitive_data_types) "Email"
set z1_request_data(sensitive_in_headers) true
}
# DOB check with multiple format support
if {[regexp $static::dob_regex $header_value dob]} {
if {[catch {
switch -regexp $dob {
{\d{4}-\d{2}-\d{2}} {
scan $dob {%d-%d-%d} year month day
}
{\d{2}/\d{2}/\d{4}} {
scan $dob {%d/%d/%d} month day year
}
{\d{2}-\d{2}-\d{4}} {
scan $dob {%d-%d-%d} month day year
}
{\d{2}\.\d{2}\.\d{4}} {
scan $dob {%d.%d.%d} month day year
}
}
if {$month >= 1 && $month <= 12 &&
$day >= 1 && $day <= 31 &&
$year >= $static::valid_years_start &&
$year <= $static::valid_years_end} {
lappend z1_request_data(sensitive_data_types) "DOB"
set z1_request_data(sensitive_in_headers) true
}
} err]} {
log local0.error "Error processing DOB: $err"
}
}
}
} err]} {
log local0.error "Error processing headers: $err"
}
# Collect payload if present
if {[HTTP::header "Content-Length"] ne ""} {
if {[catch {
set z1_req_length [HTTP::header "Content-Length"]
if {[string is integer -strict $z1_req_length] && $z1_req_length > 0} {
HTTP::collect $z1_req_length
}
} err]} {
log local0.error "Error collecting payload: $err"
}
}
}
} err]} {
log local0.error "Error in HTTP_REQUEST: $err"
}
}
when HTTP_REQUEST_DATA {
if { $processRequest == 0} { return }
if {[catch {
if {[HTTP::payload length] > 0} {
set z1_payload [HTTP::payload]
# Determine payload type efficiently
if {[catch {
if {$z1_request_data(http_version) eq "2.0" && $z1_request_data(req_content_type) eq "application/grpc"} {
set z1_request_data(payload_type) "gRPC"
} else {
set trimmed_payload [string trimleft $z1_payload]
set first_char [string index $trimmed_payload 0]
switch -exact -- $first_char {
"\[" { set z1_request_data(payload_type) "REST_ARRAY" }
"\{" { set z1_request_data(payload_type) "REST" }
"<" {
if {[string first "<soap" $z1_payload] != -1} {
set z1_request_data(payload_type) "SOAP"
} elseif {[string first "<?xml" $z1_payload] == 0} {
set z1_request_data(payload_type) "XML"
}
}
}
}
} err]} {
log local0.error "Error determining payload type: $err"
}
# Raw data pattern scanning (common for all types)
if {[catch {
if {[regexp $static::ssn_regex $z1_payload -> ssn]} {
scan $ssn {%d-%d-%d} d1 d2 d3
if {$d1 >= 1 && $d1 <= 899 && $d1 != 666 &&
$d2 >= 1 && $d2 <= 99 &&
$d3 >= 1 && $d3 <= 9999} {
lappend z1_request_data(sensitive_data_types) "SSN"
set z1_request_data(sensitive_in_payload) true
}
}
} err]} {
log local0.error "Error processing SSN in payload: $err"
}
if {[catch {
if {[regexp $static::cc_regex $z1_payload cc_number]} {
set card_number $cc_number
if {[catch {eval $static::luhn_check} result] && $result} {
lappend z1_request_data(sensitive_data_types) "CreditCard"
set z1_request_data(sensitive_in_payload) true
}
}
} err]} {
log local0.error "Error processing credit card in payload: $err"
}
if {[catch {
if {[regexp $static::email_regex $z1_payload]} {
lappend z1_request_data(sensitive_data_types) "Email"
set z1_request_data(sensitive_in_payload) true
}
} err]} {
log local0.error "Error processing email in payload: $err"
}
if {[catch {
if {[regexp $static::dob_regex $z1_payload dob]} {
switch -regexp $dob {
{\d{4}-\d{2}-\d{2}} {
scan $dob {%d-%d-%d} year month day
}
{\d{2}/\d{2}/\d{4}} {
scan $dob {%d/%d/%d} month day year
}
{\d{2}-\d{2}-\d{4}} {
scan $dob {%d-%d-%d} month day year
}
{\d{2}\.\d{2}\.\d{4}} {
scan $dob {%d.%d.%d} month day year
}
}
if {$month >= 1 && $month <= 12 &&
$day >= 1 && $day <= 31 &&
$year >= $static::valid_years_start &&
$year <= $static::valid_years_end} {
lappend z1_request_data(sensitive_data_types) "DOB"
set z1_request_data(sensitive_in_payload) true
}
}
} err]} {
log local0.error "Error processing DOB in payload: $err"
}
# Type-specific processing
if {[catch {
switch -- $z1_request_data(payload_type) {
"REST" - "REST_ARRAY" {
foreach {pattern type} [list \
$static::json_ssn_regex "SSN" \
$static::json_dob_regex "DOB" \
$static::json_email_regex "Email"] {
if {[regexp $pattern $z1_payload]} {
if {[lsearch $z1_request_data(sensitive_data_types) $type] == -1} {
lappend z1_request_data(sensitive_data_types) $type
set z1_request_data(sensitive_in_payload) true
}
}
}
}
"XML" - "SOAP" {
if {[regexp {<[^>]+>([^<]+)} $z1_payload -> content]} {
foreach {pattern type} [list \
$static::ssn_regex "SSN" \
$static::email_regex "Email" \
$static::dob_regex "DOB"] {
if {[regexp $pattern $content]} {
if {[lsearch $z1_request_data(sensitive_data_types) $type] == -1} {
lappend z1_request_data(sensitive_data_types) $type
set z1_request_data(sensitive_in_payload) true
}
}
}
}
}
}
} err]} {
log local0.error "Error processing payload type-specific patterns: $err"
}
}
} err]} {
log local0.error "Error in HTTP_REQUEST_DATA: $err"
}
}
## Exit gracefully if this request is not marked for processing.
when LB_SELECTED {
if { $processRequest == 0} { return }
if {[catch {
set z1_request_data(pool) [LB::server]
if {$static::debug} {
log local0. "Selected pool member: $z1_request_data(pool)"
}
} err]} {
log local0.error "Error in LB_SELECTED: $err"
}
}
## Exit gracefully if this request is not marked for processing.
when HTTP_RESPONSE {
if { $processRequest == 0} { return }
# Store all HTTP response data immediately to avoid timing issues
if {[catch {
set http_status [HTTP::status]
set res_content_type [HTTP::header "Content-Type"]
set res_content_length [expr {[HTTP::header "Content-Length"] ne "" ? [HTTP::header "Content-Length"] : 0}]
if {$static::debug} {
log local0. "Processing HTTP response with status: $http_status"
}
# Build response headers JSON efficiently
if {[catch {
set header_parts [list]
foreach header_name [HTTP::header names] {
if {$header_name ne "Content-Type"} {
set header_value [HTTP::header $header_name]
lappend header_parts "\"$header_name\":\"[URI::encode $header_value]\""
}
}
set response_headers "\{[join $header_parts ,]\}"
} err]} {
log local0.error "Error processing response headers: $err"
}
# Generate RFC 5424 compliant syslog message efficiently
if {[catch {
set timestamp [clock format [clock seconds] -format "%Y-%m-%dT%T.000Z" -gmt true]
set types [join $z1_request_data(sensitive_data_types) ","]
# Properly format the sensitive data types
if {$static::debug} {
log local0. "Preparing final message with [llength $z1_request_data(sensitive_data_types)] sensitive data types"
}
# Build message parts list with previously stored data
set msg_parts [list]
lappend msg_parts "\"uri\":\"$z1_request_data(http_uri)\""
lappend msg_parts "\"host\":\"$z1_request_data(http_host)\""
lappend msg_parts "\"method\":\"$z1_request_data(http_method)\""
lappend msg_parts "\"statusCode\":\"$http_status\""
lappend msg_parts "\"reqCType\":\"$z1_request_data(req_content_type)\""
lappend msg_parts "\"resCType\":\"$res_content_type\""
lappend msg_parts "\"httpv\":\"$z1_request_data(http_version)\""
lappend msg_parts "\"hasAuthorization\":$z1_request_data(has_auth)"
lappend msg_parts "\"sensitiveInHeaders\":$z1_request_data(sensitive_in_headers)"
lappend msg_parts "\"sensitiveDataTypes\":\"$types\""
lappend msg_parts "\"sensitiveInPayload\":$z1_request_data(sensitive_in_payload)"
lappend msg_parts "\"payloadType\":\"$z1_request_data(payload_type)\""
# Construct the final message with proper JSON formatting
set msg_json "\{[join $msg_parts ,]\}"
set final_message "<118>1 $timestamp [info hostname] F5-API-Discovery - - - $msg_json\n"
if {$static::debug} {
log local0. "Sending HSL message: $msg_json"
}
## Send HSL log
if {[catch {
HSL::send $hsl $final_message
} err]} {
log local0.error "Error sending HSL message: $err"
}
} err]} {
log local0.error "Error preparing message: $err"
}
if {$static::debug} {
log local0. "Request processing completed"
}
if {[catch {
unset -nocomplain -- msg_parts msg_json final_message timestamp types header_parts
unset -nocomplain -- http_status res_content_type res_content_length response_headers
array unset z1_request_data
array unset z1_response_data
unset -nocomplain -- z1_payload
} err]} {
log local0.error "Error cleaning up variables: $err"
}
} err]} {
log local0.error "Error in HTTP_RESPONSE: $err"
}
}