Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ RUN apt-get install -y libssl-dev


FROM base AS build
COPY ./lua_resty_netacea-1.4.0-0.rockspec ./
COPY ./lua_resty_netacea-1.5.0-0.rockspec ./
COPY ./src ./src
RUN /usr/local/openresty/luajit/bin/luarocks make ./lua_resty_netacea-1.4.0-0.rockspec
RUN /usr/local/openresty/luajit/bin/luarocks make ./lua_resty_netacea-1.5.0-0.rockspec

FROM build AS test

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.nginx_lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ RUN cd /usr/src && \
make install

# Set up Netacea module
COPY ./lua_resty_netacea-1.4.0-0.rockspec ./
COPY ./lua_resty_netacea-1.5.0-0.rockspec ./
COPY ./src ./src
RUN luarocks make ./lua_resty_netacea-1.4.0-0.rockspec
RUN luarocks make ./lua_resty_netacea-1.5.0-0.rockspec

# Link CA certs so they match expected filename
RUN ln -s /etc/ssl/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt
Expand Down
49 changes: 26 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,26 +114,29 @@ NETACEA_PROTECTOR_API_URL=https://your-protector-api-url

### Environment variable default values reference

| Environment variable | Default |
| ----------------------------------- | ------------------------ |
| `NETACEA_API_KEY` | none |
| `NETACEA_CAPTCHA_COOKIE_ATTRIBUTES` | `Max-Age=86400; Path=/;` |
| `NETACEA_CAPTCHA_COOKIE_NAME` | `_mitatacaptcha` |
| `NETACEA_ENABLE_CAPTCHA_CONTENT_NEGOTIATION` | `false` |
| `NETACEA_CAPTCHA_PATH` | unset |
| `NETACEA_CHECKPOINT_SIGNAL_PATH` | unset |
| `NETACEA_COOKIE_ATTRIBUTES` | `Max-Age=86400; Path=/;` |
| `NETACEA_COOKIE_ENCRYPTION_KEY` | none |
| `NETACEA_COOKIE_NAME` | `_mitata` |
| `NETACEA_INGEST_ENABLED` | `true` |
| `NETACEA_KINESIS_ACCESS_KEY` | `""` |
| `NETACEA_KINESIS_BATCH_SIZE` | `25` |
| `NETACEA_KINESIS_BATCH_TIMEOUT` | `1.0` |
| `NETACEA_KINESIS_REGION` | `eu-west-1` |
| `NETACEA_KINESIS_SECRET_KEY` | `""` |
| `NETACEA_KINESIS_STREAM_NAME` | `""` |
| `NETACEA_PROTECTION_MODE` | `INGEST` |
| `NETACEA_PROTECTOR_API_URL` | `""` |
| `NETACEA_REAL_IP_HEADER_INDEX` | unset |
| `NETACEA_REAL_IP_HEADER` | `""` |
| `NETACEA_SECRET_KEY` | none |
| Environment variable | Default |
| --------------------------------------------- | ------------------------ |
| `NETACEA_API_KEY` | none |
| `NETACEA_BLOCKED_RESPONSE_BODY` | unset |
| `NETACEA_BLOCKED_RESPONSE_CONTENT_TYPE` | `text/plain` |
| `NETACEA_BLOCKED_RESPONSE_STATUS` | `403` |
| `NETACEA_CAPTCHA_COOKIE_ATTRIBUTES` | `Max-Age=86400; Path=/;` |
| `NETACEA_CAPTCHA_COOKIE_NAME` | `_mitatacaptcha` |
| `NETACEA_CAPTCHA_PATH` | unset |
| `NETACEA_CHECKPOINT_SIGNAL_PATH` | unset |
| `NETACEA_COOKIE_ATTRIBUTES` | `Max-Age=86400; Path=/;` |
| `NETACEA_COOKIE_ENCRYPTION_KEY` | none |
| `NETACEA_COOKIE_NAME` | `_mitata` |
| `NETACEA_ENABLE_CAPTCHA_CONTENT_NEGOTIATION` | `false` |
| `NETACEA_INGEST_ENABLED` | `true` |
| `NETACEA_KINESIS_ACCESS_KEY` | `""` |
| `NETACEA_KINESIS_BATCH_SIZE` | `25` |
| `NETACEA_KINESIS_BATCH_TIMEOUT` | `1.0` |
| `NETACEA_KINESIS_REGION` | `eu-west-1` |
| `NETACEA_KINESIS_SECRET_KEY` | `""` |
| `NETACEA_KINESIS_STREAM_NAME` | `""` |
| `NETACEA_PROTECTION_MODE` | `INGEST` |
| `NETACEA_PROTECTOR_API_URL` | `""` |
| `NETACEA_REAL_IP_HEADER_INDEX` | unset |
| `NETACEA_REAL_IP_HEADER` | `""` |
| `NETACEA_SECRET_KEY` | none |
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package = "lua_resty_netacea"
version = "1.4.0-0"
version = "1.5.0-0"
source = {
url = "git://github.com/Netacea/lua_resty_netacea",
branch = "master"
Expand Down
6 changes: 6 additions & 0 deletions src/conf/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ env NETACEA_REAL_IP_HEADER_INDEX;
env NETACEA_CHECKPOINT_SIGNAL_PATH;
env NETACEA_CAPTCHA_PATH;
env NETACEA_ENABLE_CAPTCHA_CONTENT_NEGOTIATION;
env NETACEA_BLOCKED_RESPONSE_STATUS;
env NETACEA_BLOCKED_RESPONSE_BODY;
env NETACEA_BLOCKED_RESPONSE_CONTENT_TYPE;
env NETACEA_KINESIS_ACCESS_KEY;
env NETACEA_KINESIS_SECRET_KEY;
env NETACEA_KINESIS_STREAM_NAME;
Expand Down Expand Up @@ -56,6 +59,9 @@ http {
realIpHeaderIndex = tonumber(env('NETACEA_REAL_IP_HEADER_INDEX', '')),
checkpointSignalPath = env('NETACEA_CHECKPOINT_SIGNAL_PATH'),
netaceaCaptchaPath = env('NETACEA_CAPTCHA_PATH'),
blockedResponseStatus = env('NETACEA_BLOCKED_RESPONSE_STATUS'),
blockedResponseBody = env('NETACEA_BLOCKED_RESPONSE_BODY'),
blockedResponseContentType = env('NETACEA_BLOCKED_RESPONSE_CONTENT_TYPE'),
enableCaptchaContentNegotiation = envEnabled('NETACEA_ENABLE_CAPTCHA_CONTENT_NEGOTIATION', false),
kinesisProperties = {
region = env('NETACEA_KINESIS_REGION', 'eu-west-1'),
Expand Down
58 changes: 52 additions & 6 deletions src/lua_resty_netacea.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ local Constants = require("lua_resty_netacea_constants")
local mitigation = require("lua_resty_netacea_mitigation")

local _N = {}
_N._VERSION = '1.4.0'
_N._VERSION = '1.5.0'
_N._TYPE = 'nginx'

local ngx = require 'ngx'
Expand Down Expand Up @@ -41,6 +41,15 @@ local function setInjectHeaders(protector_result)
return idType, mitigationType, captchaState
end

local function normalizeBlockedResponseStatus(value)
local status = tonumber(value)
if not status or status < 100 or status > 599 or status % 1 ~= 0 then
return ngx.HTTP_FORBIDDEN
end

return status
end

local function serveCaptchaFailOpen(body, options)
local ok, err = pcall(mitigation.serveCaptcha, body, options)
if not ok then
Expand All @@ -51,6 +60,31 @@ local function serveCaptchaFailOpen(body, options)
return true
end

local function readRequestBody()
ngx.req.read_body()

local body = ngx.req.get_body_data()
if body ~= nil then
return body
end

local body_file = ngx.req.get_body_file()
if not body_file then
return nil
end

local file, err = io.open(body_file, "rb")
if not file then
ngx.log(ngx.WARN, "NETACEA CAPTCHA - unable to read request body file: ", err)
return nil
end

local data = file:read("*a")
file:close()

return data
end

function _N:new(options)
local n = {}
setmetatable(n, self)
Expand Down Expand Up @@ -123,6 +157,12 @@ function _N:new(options)
n.checkpointSignalPath = utils.parseOption(options.checkpointSignalPath, nil)
-- global:optional:netaceaCaptchaPath
n.netaceaCaptchaPath = utils.normalizeRelativePath(utils.parseOption(options.netaceaCaptchaPath, nil))
-- global:optional:blockedResponseStatus
n.blockedResponseStatus = normalizeBlockedResponseStatus(utils.parseOption(options.blockedResponseStatus, nil))
-- global:optional:blockedResponseBody
n.blockedResponseBody = utils.parseOption(options.blockedResponseBody, nil)
-- global:optional:blockedResponseContentType
n.blockedResponseContentType = utils.parseOption(options.blockedResponseContentType, nil)
-- global:optional:enableCaptchaContentNegotiation
n.enableCaptchaContentNegotiation = options.enableCaptchaContentNegotiation == true
-- global:required:apiKey
Expand Down Expand Up @@ -257,8 +297,7 @@ end
function _N:handleCaptcha()
self:handleSession()

ngx.req.read_body()
local captcha_data = ngx.req.get_body_data()
local captcha_data = readRequestBody()
local protector_result = self.protectorClient:validateCaptcha(captcha_data)
ngx.ctx.NetaceaState.protector_result = protector_result
ngx.ctx.NetaceaState.grace_period = -1000
Expand Down Expand Up @@ -305,8 +344,15 @@ function _N:mitigate()
return nil
end
local parsed_cookie = self:handleSession()
local parsed_cookie_data = parsed_cookie.data or {}

if self.netaceaCaptchaPath and ngx.var.uri == self.netaceaCaptchaPath then
ngx.ctx.NetaceaState.bc_type = self:setBcType(
parsed_cookie_data.mat or nil,
parsed_cookie_data.mit or nil,
Constants['captchaStates'].SERVE
)
ngx.log(ngx.DEBUG, "NETACEA MITIGATE - serving configured captcha path")
local trackingId = ngx.var.arg_trackingId
--TODO: make this more lenient to all JWE tokens
if not utils.isSafeTrackingId(trackingId) then
Expand All @@ -328,8 +374,8 @@ function _N:mitigate()
local signalPathEnabled = (self.checkpointSignalPath or '') ~= ''
if signalPathEnabled and ngx.var.uri == self.checkpointSignalPath then
ngx.ctx.NetaceaState.bc_type = self:setBcType(
parsed_cookie.data.mat or nil,
parsed_cookie.data.mit or nil,
parsed_cookie_data.mat or nil,
parsed_cookie_data.mit or nil,
Constants['checkpointStates'].SIGNAL
)
ngx.exit(ngx.OK)
Expand Down Expand Up @@ -390,7 +436,7 @@ function _N:mitigate()
ngx.log(ngx.DEBUG, "NETACEA MITIGATE - serving block")
ngx.ctx.NetaceaState.grace_period = -1000
self:refreshSession(parsed_cookie.reason)
mitigation.serveBlock()
mitigation.serveBlock(self.blockedResponseStatus, self.blockedResponseBody, self.blockedResponseContentType)
return
end

Expand Down
16 changes: 12 additions & 4 deletions src/lua_resty_netacea_mitigation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,19 @@ function _M.serveCaptcha(captchaBody, options)
return ngx.exit(ngx.HTTP_OK)
end

function _M.serveBlock()
ngx.status = ngx.HTTP_FORBIDDEN;
function _M.serveBlock(blockedResponseStatus, blockedResponseBody, blockedResponseContentType)
local status = tonumber(blockedResponseStatus)
if not status or status < 100 or status > 599 or status % 1 ~= 0 then
status = ngx.HTTP_FORBIDDEN
end
local body = blockedResponseBody or (tostring(status) .. " Forbidden")
ngx.status = status;
if blockedResponseContentType then
ngx.header["content-type"] = blockedResponseContentType
end
ngx.header["Cache-Control"] = "max-age=0, no-cache, no-store, must-revalidate"
ngx.print("403 Forbidden");
return ngx.exit(ngx.HTTP_FORBIDDEN);
ngx.print(body);
return ngx.exit(status);
end

function _M.serveMonetisationRedirect(location)
Expand Down
30 changes: 30 additions & 0 deletions test/lua_resty_netacea_mitigation_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,21 @@ describe("lua_resty_netacea_mitigation", function()
assert.are.equal(403, ngx_mock.status)
end)

it("should use the configured blocked response status", function()
mitigation.serveBlock(429)
assert.are.equal(429, ngx_mock.status)
end)

it("should default to HTTP_FORBIDDEN when the blocked response status is invalid", function()
mitigation.serveBlock(700)
assert.are.equal(403, ngx_mock.status)
end)

it("should default to HTTP_FORBIDDEN when the blocked response status is not an integer", function()
mitigation.serveBlock(429.5)
assert.are.equal(403, ngx_mock.status)
end)

it("should set Cache-Control to no-cache", function()
mitigation.serveBlock()
assert.are.equal("max-age=0, no-cache, no-store, must-revalidate", ngx_mock.header["Cache-Control"])
Expand All @@ -171,6 +186,21 @@ describe("lua_resty_netacea_mitigation", function()
assert.spy(ngx_mock.print).was.called_with("403 Forbidden")
end)

it("should print the configured blocked response status", function()
mitigation.serveBlock(429)
assert.spy(ngx_mock.print).was.called_with("429 Forbidden")
end)

it("should print the configured blocked response body verbatim", function()
mitigation.serveBlock(429, "Too many requests")
assert.spy(ngx_mock.print).was.called_with("Too many requests")
end)

it("should set the configured blocked response content type", function()
mitigation.serveBlock(429, "Too many requests", "text/plain; charset=utf-8")
assert.are.equal("text/plain; charset=utf-8", ngx_mock.header["content-type"])
end)

it("should exit with HTTP_FORBIDDEN", function()
mitigation.serveBlock()
assert.spy(ngx_mock.exit).was.called_with(403)
Expand Down
Loading