rodsec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rvmrc +60 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +15 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/examples/body.yml +5 -0
- data/examples/modsec.ru +40 -0
- data/ext/msc_intervention/extconf.rb +8 -0
- data/ext/msc_intervention/msc_intervention.cc +48 -0
- data/lib/rodsec.rb +18 -0
- data/lib/rodsec/modsec.rb +60 -0
- data/lib/rodsec/rack.rb +152 -0
- data/lib/rodsec/read_config.rb +80 -0
- data/lib/rodsec/rule_set.rb +69 -0
- data/lib/rodsec/string_pointers.rb +10 -0
- data/lib/rodsec/transaction.rb +168 -0
- data/lib/rodsec/version.rb +3 -0
- data/lib/rodsec/wrapper.rb +136 -0
- data/rodsec.gemspec +40 -0
- data/spec/config/crs-setup.conf +787 -0
- data/spec/config/modsecurity.conf +264 -0
- metadata +156 -0
data/lib/rodsec/rack.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require_relative '../rodsec.rb'
|
2
|
+
|
3
|
+
module Rodsec
|
4
|
+
# Thanks to rack-contrib/deflect for the basic idea, and some of the docs.
|
5
|
+
class Rack
|
6
|
+
# === Required Options:
|
7
|
+
#
|
8
|
+
# :config Proc, or the directory containing the ModSecurity config files
|
9
|
+
# modsecurity.conf and crs-setup.conf. If it's a Proc, which
|
10
|
+
# must return a Rodsec::Ruleset instance containing all the
|
11
|
+
# rules you want.
|
12
|
+
#
|
13
|
+
# === Optional Options:
|
14
|
+
#
|
15
|
+
# :rules the directory containing the ModSecurity rules files.
|
16
|
+
# Defaults to ${config}/rules. Ignored if you pass a proc to config
|
17
|
+
#
|
18
|
+
# :logger must respond_to #puts which takes a string. Defaults to a StringIO at #logger
|
19
|
+
#
|
20
|
+
# :log_blk a callable that takes |tag,string| Defaults to sending
|
21
|
+
# only the string to logger. The ModSecurity logs are highly
|
22
|
+
# structured and you might want to parse them, so the tag
|
23
|
+
# helps disambiguate the source of the logs.
|
24
|
+
#
|
25
|
+
# ? :msi_blk called with [status, headers, body] if there's an intervention from ModSecurity.
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# === Examples:
|
29
|
+
#
|
30
|
+
# use Rodsec::Rack, config: 'your_config_path', log: (mylogger = StringIO.new)
|
31
|
+
# use Rodsec::Rack, config: 'your_config_path', log_blk: -> src_class, str { my_funky_parse_msi_to_hash str }
|
32
|
+
def initialize app, config:, rules: nil, logger: nil, log_blk: nil
|
33
|
+
@app = app
|
34
|
+
|
35
|
+
@log_blk = log_blk || -> _tag, str{self.logger.puts str}
|
36
|
+
@msc = Rodsec::Modsec.new{|tag,str| @log_blk.call tag, str}
|
37
|
+
|
38
|
+
@logger = logger || StringIO.new
|
39
|
+
|
40
|
+
@log_blk.call self.class, "#{self.class} starting with #{@msc.version_info}"
|
41
|
+
|
42
|
+
set_rules config, rules
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :log_blk, :logger
|
46
|
+
|
47
|
+
include ReadConfig
|
48
|
+
|
49
|
+
protected def set_rules config, rules
|
50
|
+
case config
|
51
|
+
when Proc
|
52
|
+
@rules = config.call
|
53
|
+
else
|
54
|
+
@rules = read_config config, rules, &log_blk
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
REQUEST_URI = 'REQUEST_URI'.freeze
|
59
|
+
REMOTE_HOST = 'REMOTE_HOST'.freeze
|
60
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
61
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
62
|
+
HTTP_HOST = 'HTTP_HOST'.freeze
|
63
|
+
SERVER_PORT = 'SERVER_PORT'.freeze
|
64
|
+
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
65
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
66
|
+
SLASH = '/'.freeze
|
67
|
+
HTTP_HEADER_RX = /HTTP_(.*)|(CONTENT_.*)/.freeze
|
68
|
+
DASH = '-'.freeze
|
69
|
+
UNDERSCORE = '_'.freeze
|
70
|
+
EMPTY = String.new.freeze
|
71
|
+
|
72
|
+
RACK_INPUT = 'rack.input'.freeze
|
73
|
+
|
74
|
+
def call env
|
75
|
+
txn = Rodsec::Transaction.new @msc, @rules, txn_log_tag: env[REQUEST_URI]
|
76
|
+
|
77
|
+
################
|
78
|
+
# incoming
|
79
|
+
|
80
|
+
# uri! scope for variables
|
81
|
+
lambda do
|
82
|
+
remote_addr = env[REMOTE_HOST] || env[REMOTE_ADDR]
|
83
|
+
server_addr = env[HTTP_HOST] || env[SERVER_NAME]
|
84
|
+
txn.connection! remote_addr, 0, server_addr, (env[SERVER_PORT] || 0)
|
85
|
+
|
86
|
+
_, version = env[HTTP_VERSION]&.split(SLASH)
|
87
|
+
|
88
|
+
txn.uri! env[REQUEST_URI], env[REQUEST_METHOD], version
|
89
|
+
end.call
|
90
|
+
|
91
|
+
# request_headers! - another scope for variables
|
92
|
+
lambda do
|
93
|
+
http_headers = env.map do |key,val|
|
94
|
+
key =~ HTTP_HEADER_RX or next
|
95
|
+
header_name = $1 || $2
|
96
|
+
dashified = header_name.split(UNDERSCORE).map(&:capitalize).join(DASH)
|
97
|
+
[dashified, val]
|
98
|
+
end.compact.to_h
|
99
|
+
|
100
|
+
txn.request_headers! http_headers
|
101
|
+
end.call
|
102
|
+
|
103
|
+
# request_body! MUST be called (even with an empty body is fine),
|
104
|
+
# otherwise ModSecurity never triggers the rules, even though ModSecurity
|
105
|
+
# can detect something dodgy in the headers. That needs what they call
|
106
|
+
# self-contained mode.
|
107
|
+
env[RACK_INPUT].tap do |rack_input|
|
108
|
+
# ruby-2.3 syntax :-|
|
109
|
+
begin
|
110
|
+
# What about a DOS from a very large body?
|
111
|
+
#
|
112
|
+
# Rack spec says rack.input must be rewindable at the http-server
|
113
|
+
# level, so it's all in memory by now anyway, nothing we can do to
|
114
|
+
# affect that here.
|
115
|
+
txn.request_body! rack_input
|
116
|
+
ensure
|
117
|
+
# Have to rewind input, otherwise other rack apps can't get the content
|
118
|
+
rack_input.rewind
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
################
|
123
|
+
# rack chain
|
124
|
+
status, headers, body = @app.call env
|
125
|
+
|
126
|
+
################
|
127
|
+
# outgoing
|
128
|
+
txn.response_headers! status, env[HTTP_VERSION], headers
|
129
|
+
|
130
|
+
# TODO handle hijacking? Not sure.
|
131
|
+
# body is an Enumerable, which response_body! will handle
|
132
|
+
txn.response_body! body
|
133
|
+
|
134
|
+
# Logging. From ModSecurity's point of view this could be in a separate
|
135
|
+
# thread. Dunno how rack will handle that though. Also, there's no way to
|
136
|
+
# wait for a thread doing that logging. So it would have to be spawned and
|
137
|
+
# then left to die. Alone. In the rain.
|
138
|
+
txn.logging
|
139
|
+
|
140
|
+
# all ok
|
141
|
+
return status, headers, body
|
142
|
+
|
143
|
+
rescue Rodsec::Intervention => iex
|
144
|
+
log_blk.call :intervention, iex.msi.log
|
145
|
+
# rack interface specification says we have to call close on the body, if
|
146
|
+
# it responds to close
|
147
|
+
body.respond_to?(:close) && body.close
|
148
|
+
# Intervention!
|
149
|
+
return iex.msi.status, {'Content-Type' => 'text/plain'}, [ ::Rack::Utils::HTTP_STATUS_CODES[iex.msi.status] ].compact
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Rodsec
|
2
|
+
module ReadConfig
|
3
|
+
# log_blk takes a tag and a string
|
4
|
+
module_function def read_config config, rules, &log_blk
|
5
|
+
config_dir = Pathname config
|
6
|
+
rules_dir = Pathname(rules || config_dir + 'rules')
|
7
|
+
|
8
|
+
# NOTE the first two config files MUST be loaded before the rules files
|
9
|
+
config_rules = RuleSet.new
|
10
|
+
config_rules.add_file config_dir + 'modsecurity.conf'
|
11
|
+
config_rules.add_file config_dir + 'crs-setup.conf'
|
12
|
+
|
13
|
+
# Now load the rules files
|
14
|
+
rules_files = rules_dir.children.select{|p| p.to_s =~ /.*conf$/}.sort
|
15
|
+
|
16
|
+
# merge rules files.
|
17
|
+
rules_files.reduce config_rules do |ax, fn|
|
18
|
+
# ruby 2.3.x syntax :-|
|
19
|
+
begin
|
20
|
+
log_blk&.call self.class, "loading rules file: #{fn}"
|
21
|
+
rules = RuleSet.new tag: fn
|
22
|
+
rules.add_file fn
|
23
|
+
ax.merge rules
|
24
|
+
rescue
|
25
|
+
log_blk&.call $!.class, "error loading rules file: #{$!}"
|
26
|
+
ax
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Hacky Workaround for the bug that's tickled by merging Rules.
|
32
|
+
#
|
33
|
+
# What we do is read all the files separately. Check each one to see that it
|
34
|
+
# has no syntax errors. If that works, append the file contents to one giant
|
35
|
+
# string containing all the rule files. When we've read all the rule files,
|
36
|
+
# create one RuleSet from the combined string. That way we don't need to
|
37
|
+
# merge rulesets.
|
38
|
+
module_function def read_combined_config config, rules, &log_blk
|
39
|
+
# part of the hacky workaround, so it's self-contained when we remove it.
|
40
|
+
require 'stringio'
|
41
|
+
config_dir = Pathname config
|
42
|
+
rules_dir = Pathname(rules || config_dir + 'rules')
|
43
|
+
|
44
|
+
# NOTE the first two config files MUST be loaded before the rules files
|
45
|
+
files = [(config_dir + 'modsecurity.conf'), (config_dir + 'crs-setup.conf')]
|
46
|
+
|
47
|
+
# Now add the rules files
|
48
|
+
files.concat rules_dir.children.select{|p| p.to_s =~ /.*conf$/}.sort
|
49
|
+
|
50
|
+
# merge rules files.
|
51
|
+
combined_rules = files.each_with_object StringIO.new do |fn, sio|
|
52
|
+
begin
|
53
|
+
log_blk&.call self.class, "loading rules file: #{fn}"
|
54
|
+
|
55
|
+
# syntax check rule set
|
56
|
+
RuleSet.new.add_file fn.to_s
|
57
|
+
|
58
|
+
File.open fn do |io| IO.copy_stream io, sio end
|
59
|
+
rescue
|
60
|
+
log_blk&.call $!.class, "error loading rules file: #{$!}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# make sure the rules can access their *.data files - we lose the file
|
65
|
+
# location information when we use this approach.
|
66
|
+
save_dir = Dir.pwd
|
67
|
+
Dir.chdir rules_dir
|
68
|
+
|
69
|
+
# add the combined rules
|
70
|
+
log_blk&.call self.class, 'loading combined rules'
|
71
|
+
p size: combined_rules.string.length
|
72
|
+
rules = RuleSet.new
|
73
|
+
rules.add combined_rules.string
|
74
|
+
rules
|
75
|
+
ensure
|
76
|
+
# restore original directory
|
77
|
+
save_dir and Dir.chdir save_dir
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
require_relative 'wrapper'
|
4
|
+
require_relative 'string_pointers'
|
5
|
+
|
6
|
+
module Rodsec
|
7
|
+
class RuleSet
|
8
|
+
def initialize tag: nil
|
9
|
+
@tag = tag
|
10
|
+
|
11
|
+
@rules_ptr = Wrapper.msc_create_rules_set
|
12
|
+
@rules_ptr.free = Wrapper['msc_rules_cleanup']
|
13
|
+
|
14
|
+
# just mirroring the c-api, not sure if it's actually useful
|
15
|
+
@rule_count = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# srsly, don't mess with this
|
19
|
+
attr_reader :rules_ptr
|
20
|
+
|
21
|
+
attr_reader :tag, :rule_count
|
22
|
+
|
23
|
+
include StringPointers
|
24
|
+
|
25
|
+
# add rules from the given file
|
26
|
+
# return number of rules added? I think?
|
27
|
+
def add_file conf_pathname
|
28
|
+
conf_pathname = Pathname conf_pathname
|
29
|
+
err = Fiddle::Pointer[0]
|
30
|
+
rv = Wrapper.msc_rules_add_file rules_ptr, (strptr conf_pathname.realpath.to_s), err.ref
|
31
|
+
|
32
|
+
raise Error, [conf_pathname, err.to_s] if rv < 0
|
33
|
+
@rule_count += rv
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# dump rules to stdout. No way to redirect that, from what I can see.
|
38
|
+
def dump
|
39
|
+
Wrapper.msc_rules_dump rules_ptr
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def add rules_text
|
44
|
+
err = Fiddle::Pointer[0]
|
45
|
+
rv = Wrapper.msc_rules_add rules_ptr, (strptr rules_text.to_s), err.ref
|
46
|
+
raise Error, err.to_s if rv < 0
|
47
|
+
@rule_count += rv
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_url key, url
|
52
|
+
err = Fiddle::Pointer[0]
|
53
|
+
rv = Wrapper.msc_rules_add_remote rules_ptr, (strptr key), (strptr uri), err.ref
|
54
|
+
raise Error, err.to_s if rv < 0
|
55
|
+
@rule_count += rv
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# merge other rules with self
|
60
|
+
def merge other
|
61
|
+
raise "must be a #{self.class.name}" unless self.class === other
|
62
|
+
err = Fiddle::Pointer[0]
|
63
|
+
rv = Wrapper.msc_rules_merge rules_ptr, other.rules_ptr, err.ref
|
64
|
+
@rule_count += rv
|
65
|
+
raise Error, err.to_s if rv < 0
|
66
|
+
self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require_relative 'wrapper'
|
2
|
+
require_relative 'string_pointers'
|
3
|
+
|
4
|
+
module Rodsec
|
5
|
+
class Transaction
|
6
|
+
# txn_log_tag must be convertible to a string. Defaults to self.object_id.to_s
|
7
|
+
# it shows up in as the first argument passed to Modsec's log_blk.
|
8
|
+
def initialize msc, ruleset, txn_log_tag: nil
|
9
|
+
raise Error, "msc must be a #{Modsec}" unless Modsec === msc
|
10
|
+
raise Error, "ruleset must be a #{RuleSet}" unless RuleSet === ruleset
|
11
|
+
|
12
|
+
@msc, @ruleset = msc, ruleset
|
13
|
+
@txn_log_tag = Fiddle::Pointer[(txn_log_tag || object_id).to_s]
|
14
|
+
@txn_ptr = Wrapper.msc_new_transaction msc.msc_ptr, ruleset.rules_ptr, @txn_log_tag
|
15
|
+
@txn_ptr.free = Wrapper['msc_transaction_cleanup']
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :txn_ptr
|
19
|
+
|
20
|
+
attr_reader :msc, :ruleset
|
21
|
+
|
22
|
+
include StringPointers
|
23
|
+
|
24
|
+
# Raise an Intervention(ModSecurityIntervention) if necessary, or return self.
|
25
|
+
#
|
26
|
+
# ModSecurity will only populate the intervention structure if it detects
|
27
|
+
# something 'disruptive' in the SecRules.
|
28
|
+
protected def intervention!
|
29
|
+
# Check for Intervention
|
30
|
+
msi = Wrapper::ModSecurityIntervention.new Wrapper.msc_new_intervention
|
31
|
+
rv = Wrapper.msc_intervention txn_ptr, msi
|
32
|
+
raise Intervention, msi if rv > 0
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
##################################
|
37
|
+
# Phase CONNECTION / SecRules 0
|
38
|
+
# check for intervention afterwards
|
39
|
+
def connection! client_host, client_port, server_host, server_port
|
40
|
+
rv = Wrapper.msc_process_connection \
|
41
|
+
txn_ptr,
|
42
|
+
(strptr client_host), (Integer client_port),
|
43
|
+
(strptr server_host), (Integer server_port)
|
44
|
+
|
45
|
+
rv == 1 or raise Error, "msc_process_connection failed for #{[client_host, client_port, server_host, server_port].inspect}"
|
46
|
+
|
47
|
+
intervention!
|
48
|
+
end
|
49
|
+
|
50
|
+
##################################
|
51
|
+
# Phase URI / 1.5
|
52
|
+
# check for intervention afterwards
|
53
|
+
# verb is GET POST etc
|
54
|
+
# http_version is '1.1', '1.2' etc
|
55
|
+
def uri! uri, verb, http_version
|
56
|
+
rv = Wrapper.msc_process_uri txn_ptr, (strptr uri), (strptr verb), (strptr http_version)
|
57
|
+
rv == 1 or raise Error "msc_process_uri failed for #{[uri, verb, http_version].inspect}"
|
58
|
+
|
59
|
+
intervention!
|
60
|
+
end
|
61
|
+
|
62
|
+
##################################
|
63
|
+
# Phase REQUEST_HEADERS. SecRules 1
|
64
|
+
def request_headers! header_hash
|
65
|
+
errors = header_hash.each_with_object [] do |(key, val), errors|
|
66
|
+
key = key.to_s; val = val.to_s
|
67
|
+
rv = Wrapper.msc_add_n_request_header txn_ptr, (strptr key), key.bytesize, (strptr val), val.bytesize
|
68
|
+
rv == 1 or errors << "msc_add_n_request_header failed adding #{[key,val].inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
raise Error errors if errors.any?
|
72
|
+
|
73
|
+
rv = Wrapper.msc_process_request_headers txn_ptr
|
74
|
+
rv == 1 or raise "msc_process_request_headers failed"
|
75
|
+
|
76
|
+
intervention!
|
77
|
+
end
|
78
|
+
|
79
|
+
protected def enum_of_body body
|
80
|
+
case
|
81
|
+
when NilClass === body
|
82
|
+
['']
|
83
|
+
when String === body
|
84
|
+
[body]
|
85
|
+
when body.respond_to?(:each)
|
86
|
+
body
|
87
|
+
else
|
88
|
+
raise "dunno about #{body}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##################################
|
93
|
+
# Phase REQUEST_BODY. SecRules 2
|
94
|
+
#
|
95
|
+
# body can be a String, or an Enumerable of strings
|
96
|
+
def request_body! body
|
97
|
+
enum_of_body(body).each do |body_part|
|
98
|
+
body_part = body_part.to_s
|
99
|
+
rv = Wrapper.msc_append_request_body txn_ptr, (strptr body_part), body_part.bytesize
|
100
|
+
rv == 1 or raise Error, "msc_append_request_body failed"
|
101
|
+
end
|
102
|
+
|
103
|
+
# This MUST be called, otherwise rules aren't triggered.
|
104
|
+
rv = Wrapper.msc_process_request_body txn_ptr
|
105
|
+
rv == 1 or raise Error, "msc_process_request_body failed"
|
106
|
+
|
107
|
+
intervention!
|
108
|
+
end
|
109
|
+
|
110
|
+
# This is probably only used when appending a body in chunks. We don't use it.
|
111
|
+
# extern 'size_t msc_get_request_body_length(Transaction *transaction)'
|
112
|
+
|
113
|
+
##################################
|
114
|
+
# Phase RESPONSE_HEADERS. SecRules 3
|
115
|
+
# http_status_code is one of the 200, 401, 404 etc codes
|
116
|
+
# http_with_version seems to be things like 'HTTP 1.2', not entirely sure.
|
117
|
+
def response_headers! http_status_code = 200, http_with_version = 'HTTP 1.1', header_hash
|
118
|
+
errors = header_hash.each_with_object [] do |(key, val), errors|
|
119
|
+
key = key.to_s; val = val.to_s
|
120
|
+
rv = Wrapper.msc_add_n_response_header txn_ptr, (strptr key), key.bytesize, (strptr val), val.bytesize
|
121
|
+
rv == 1 or errors << "msc_add_n_response_header failed adding #{[key,val].inspect}"
|
122
|
+
end
|
123
|
+
|
124
|
+
raise Error, errors if errors.any?
|
125
|
+
|
126
|
+
rv = Wrapper.msc_process_response_headers txn_ptr, (Integer http_status_code), (strptr http_with_version)
|
127
|
+
rv == 1 or raise "msc_process_response_headers failed"
|
128
|
+
|
129
|
+
intervention!
|
130
|
+
end
|
131
|
+
|
132
|
+
# Called after msc_process_response_headers "to inform a new response code"
|
133
|
+
# Not mandatory. Not sure what it means really. Maybe it affects the intervention values?
|
134
|
+
# extern 'int msc_update_status_code(Transaction *transaction, int status)'
|
135
|
+
|
136
|
+
##################################
|
137
|
+
# Phase RESPONSE_BODY. SecRules 4
|
138
|
+
#
|
139
|
+
# body can be a String, or an Enumerable of strings
|
140
|
+
def response_body! body
|
141
|
+
enum_of_body(body).each do |body_part|
|
142
|
+
body_part = body_part.to_s
|
143
|
+
rv = Wrapper.msc_append_response_body txn_ptr, (strptr body_part), body_part.bytesize
|
144
|
+
rv == 1 or raise Error, 'msc_append_response_body failed'
|
145
|
+
end
|
146
|
+
|
147
|
+
# This MUST be called, otherwise rules aren't triggered
|
148
|
+
rv = Wrapper.msc_process_response_body txn_ptr
|
149
|
+
rv == 1 or raise Error, "msc_process_response_body failed"
|
150
|
+
|
151
|
+
intervention!
|
152
|
+
end
|
153
|
+
|
154
|
+
# Needed if ModSecurity modifies the outgoing body. We don't make use of that.
|
155
|
+
# extern 'const char *msc_get_response_body(Transaction *transaction)'
|
156
|
+
# extern 'size_t msc_get_response_body_length(Transaction *transaction)'
|
157
|
+
|
158
|
+
##################################
|
159
|
+
# Phase LOGGING. SecRules 5.
|
160
|
+
# just logs all information. Response can be sent prior to this, or concurrently.
|
161
|
+
def logging
|
162
|
+
rv = Wrapper.msc_process_logging txn_ptr
|
163
|
+
rv == 1 or raise 'msc_process_logging failed'
|
164
|
+
|
165
|
+
self
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|