| -- Licensed to the Apache Software Foundation (ASF) under one |
| -- or more contributor license agreements. See the NOTICE file |
| -- distributed with this work for additional information |
| -- regarding copyright ownership. The ASF licenses this file |
| -- to you under the Apache License, Version 2.0 (the |
| -- "License"); you may not use this file except in compliance |
| -- with the License. You may obtain a copy of the License at |
| -- |
| -- http://www.apache.org/licenses/LICENSE-2.0 |
| -- |
| -- Unless required by applicable law or agreed to in writing, software |
| -- distributed under the License is distributed on an "AS IS" BASIS, |
| -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| -- See the License for the specific language governing permissions and |
| -- limitations under the License. |
| |
| |
| ts.add_package_path('/usr/local/var/lua/?.lua') |
| |
| local msc = require("msc") |
| local msc_config = require("msc_config") |
| local ffi = require("ffi") |
| local C = require("C") |
| |
| local mst = msc.msc_init() |
| msc.msc_set_connector_info(mst, "ModSecurity-ats") |
| |
| -- Initialization. Load the modsecurity configuration passed to this lua module |
| function __init__(argtb) |
| if (#argtb) < 1 then |
| ts.error("No ModSecurity Conf is given") |
| return -1 |
| end |
| |
| msc_config.rulesfile = argtb[1] |
| ts.debug("ModSecurity Conf file is " .. msc_config.rulesfile) |
| |
| msc_config.rules = msc.msc_create_rules_set() |
| local error = ffi.new("const char *[1]") |
| local result = msc.msc_rules_add_file(msc_config.rules, msc_config.rulesfile, error) |
| if(result < 0) then |
| ts.error("Problems loading the rules: ".. ffi.string(error[0])) |
| local ptr = ffi.cast("void *", error[0]) |
| C.free(ptr) |
| |
| msc.msc_rules_cleanup(msc_config.rules) |
| msc_config.rules = nil |
| return -1 |
| end |
| |
| end |
| |
| -- Reload modsecurity configuration. Trigger by "traffic_ctl config reload" |
| function __reload__() |
| ts.debug("Reloading ModSecurity Conf: " .. msc_config.rulesfile) |
| |
| newrules = msc.msc_create_rules_set() |
| local error = ffi.new("const char *[1]") |
| local result = msc.msc_rules_add_file(newrules, msc_config.rulesfile, error) |
| if(result < 0) then |
| ts.error("Problems loading the rules during reload: ".. ffi.string(error[0])) |
| local ptr = ffi.cast("void *", error[0]) |
| C.free(ptr) |
| |
| msc.msc_rules_cleanup(newrules) |
| newrules = nil |
| else |
| -- TODO: we are not doing clean up on the old rules and thus leaking resources here |
| msc_config.rules = newrules |
| end |
| |
| end |
| |
| -- Entry point function run for each incoming request |
| function do_global_read_request() |
| if(msc_config.rules == nil) then |
| ts.debug("No rules loaded. Thus there is no processing done") |
| return 0 |
| end |
| local txn = msc.msc_new_transaction(mst, msc_config.rules ,nil) |
| |
| -- processing for the connection information |
| local client_ip, client_port, client_ip_family = ts.client_request.client_addr.get_addr() |
| local incoming_port = ts.client_request.client_addr.get_incoming_port() |
| msc.msc_process_connection(txn, client_ip, client_port, "127.0.0.1", incoming_port) |
| |
| -- processing for the uri information |
| local uri = ts.client_request.get_uri() |
| local query_params = ts.client_request.get_uri_args() or '' |
| if (query_params ~= '') then |
| uri = uri .. '?' .. query_params |
| end |
| msc.msc_process_uri(txn, uri, ts.client_request.get_method(), ts.client_request.get_version()) |
| |
| -- processing for the request headers |
| local hdrs = ts.client_request.get_headers() |
| for k, v in pairs(hdrs) do |
| msc.msc_add_request_header(txn, k, v) |
| end |
| msc.msc_process_request_headers(txn) |
| msc.msc_process_request_body(txn) |
| ts.debug("done with processing request") |
| |
| -- detect if intervention is needed |
| local iv = ffi.new("ModSecurityIntervention") |
| iv.status = 200 |
| iv.log = nil |
| iv.url = nil |
| iv.disruptive = 0 |
| local iv_res = msc.msc_intervention(txn, iv) |
| ts.debug("done with intervention ".. iv_res .. ' with status ' .. iv.status ) |
| |
| if(iv.log ~= nil) then |
| ts.debug("Intervention log: " .. ffi.string(iv.log)) |
| C.free(iv.log) |
| end |
| |
| -- if found an intervention url, trigger handler when sending response to client |
| if(iv.url ~= nil) then |
| ts.ctx['url'] = ffi.string(iv.url) |
| ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, send_response) |
| C.free(iv.url) |
| end |
| |
| -- intervention is needed if status is not 200 |
| if (iv.status ~= 200) then |
| ts.http.set_resp(iv.status) |
| msc.msc_process_logging(txn) |
| msc.msc_transaction_cleanup(txn) |
| ts.debug("done with setting custom response") |
| return 0 |
| end |
| |
| -- storing modsecurity object in context |
| ts.ctx["mst"] = txn |
| ts.debug("done with setting context") |
| |
| -- trigger handler to run when response is received |
| ts.hook(TS_LUA_HOOK_READ_RESPONSE_HDR, read_response) |
| |
| return 0 |
| end |
| |
| -- function run when response is received from origin |
| function read_response() |
| -- retrieving modsecurity object |
| local txn = ts.ctx["mst"] |
| |
| if(txn == nil) then |
| ts.error("no transaction object") |
| return 0 |
| end |
| |
| -- processing for the response headers |
| local hdrs = ts.server_response.get_headers() |
| for k, v in pairs(hdrs) do |
| msc.msc_add_response_header(txn, k, v) |
| end |
| msc.msc_process_response_headers(txn, ts.server_response.get_status(), "HTTP/"..ts.server_response.get_version()) |
| msc.msc_process_response_body(txn) |
| ts.debug("done with processing response") |
| |
| -- determine if intervention is needed |
| local iv = ffi.new("ModSecurityIntervention") |
| iv.status = 200 |
| iv.log = nil |
| iv.url = nil |
| iv.disruptive = 0 |
| local iv_res = msc.msc_intervention(txn, iv) |
| ts.debug("done with intervention ".. iv_res .. ' with status ' .. iv.status ) |
| |
| if(iv.log ~= nil) then |
| ts.debug("Intervention log: " .. ffi.string(iv.log)) |
| C.free(iv.log) |
| end |
| |
| -- if found an intervention url, trigger handler when sending response to client |
| ts.ctx['url'] = '' |
| if(iv.url ~= nil) then |
| ts.ctx['url'] = ffi.string(iv.url) |
| C.free(iv.url) |
| end |
| |
| -- intervention needed when status is not 200 |
| ts.ctx['status'] = nil |
| if (iv.status ~= 200) then |
| ts.ctx['status'] = iv.status |
| end |
| |
| -- response needs to be modified? |
| if(ts.ctx['url'] ~= '' or ts.ctx['status'] ~= nil) then |
| ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, send_response) |
| end |
| |
| -- we need to return -1 for lua to reset response body with an error body |
| if(ts.ctx['status'] ~= nil) then |
| ts.ctx["mst"] = nil |
| msc.msc_process_logging(txn) |
| msc.msc_transaction_cleanup(txn) |
| ts.debug("done with cleaning up context and return error response") |
| return -1 |
| end |
| |
| -- cleaning up |
| ts.ctx["mst"] = nil |
| msc.msc_process_logging(txn) |
| msc.msc_transaction_cleanup(txn) |
| ts.debug("done with cleaning up context") |
| |
| return 0 |
| end |
| |
| -- function run when sending response to client |
| function send_response() |
| -- retrieve intervention url and add it as "Location" header on response to client |
| local location = ts.ctx['url'] |
| if (location ~= '') then |
| ts.debug('location: ' .. location) |
| ts.client_response.header['Location'] = location |
| end |
| |
| -- retrieve status and reset the response with it |
| local status = ts.ctx['status'] |
| if (status ~= nil) then |
| ts.client_response.set_error_resp(status, 'Contents Reset by ModSecurity\n') |
| end |
| end |