Skip to content
Open
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.5.0-0.rockspec ./
COPY ./lua_resty_netacea-1.6.0-0.rockspec ./
COPY ./src ./src
RUN /usr/local/openresty/luajit/bin/luarocks make ./lua_resty_netacea-1.5.0-0.rockspec
RUN /usr/local/openresty/luajit/bin/luarocks make ./lua_resty_netacea-1.6.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.5.0-0.rockspec ./
COPY ./lua_resty_netacea-1.6.0-0.rockspec ./
COPY ./src ./src
RUN luarocks make ./lua_resty_netacea-1.5.0-0.rockspec
RUN luarocks make ./lua_resty_netacea-1.6.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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ NETACEA_PROTECTOR_API_URL=https://your-protector-api-url
| `NETACEA_CAPTCHA_COOKIE_ATTRIBUTES` | `Max-Age=86400; Path=/;` |
| `NETACEA_CAPTCHA_COOKIE_NAME` | `_mitatacaptcha` |
| `NETACEA_CAPTCHA_PATH` | unset |
| `NETACEA_CHALLENGE_RESPONSE_STATUS` | `403` |
| `NETACEA_CHECKPOINT_SIGNAL_PATH` | unset |
| `NETACEA_COOKIE_ATTRIBUTES` | `Max-Age=86400; Path=/;` |
| `NETACEA_COOKIE_ENCRYPTION_KEY` | none |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package = "lua_resty_netacea"
version = "1.5.0-0"
version = "1.6.0-0"
source = {
url = "git://github.com/Netacea/lua_resty_netacea",
branch = "master"
Expand Down
2 changes: 2 additions & 0 deletions src/conf/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ env NETACEA_REAL_IP_HEADER_INDEX;
env NETACEA_CHECKPOINT_SIGNAL_PATH;
env NETACEA_CAPTCHA_PATH;
env NETACEA_ENABLE_CAPTCHA_CONTENT_NEGOTIATION;
env NETACEA_CHALLENGE_RESPONSE_STATUS;
env NETACEA_BLOCKED_RESPONSE_STATUS;
env NETACEA_BLOCKED_RESPONSE_BODY;
env NETACEA_BLOCKED_RESPONSE_CONTENT_TYPE;
Expand Down Expand Up @@ -59,6 +60,7 @@ http {
realIpHeaderIndex = tonumber(env('NETACEA_REAL_IP_HEADER_INDEX', '')),
checkpointSignalPath = env('NETACEA_CHECKPOINT_SIGNAL_PATH'),
netaceaCaptchaPath = env('NETACEA_CAPTCHA_PATH'),
challengeResponseStatus = env('NETACEA_CHALLENGE_RESPONSE_STATUS'),
blockedResponseStatus = env('NETACEA_BLOCKED_RESPONSE_STATUS'),
blockedResponseBody = env('NETACEA_BLOCKED_RESPONSE_BODY'),
blockedResponseContentType = env('NETACEA_BLOCKED_RESPONSE_CONTENT_TYPE'),
Expand Down
17 changes: 11 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.5.0'
_N._VERSION = '1.6.0'
_N._TYPE = 'nginx'

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

local function normalizeBlockedResponseStatus(value)
local function normalizeHttpStatus(value)
local status = tonumber(value)
if not status or status < 100 or status > 599 or status % 1 ~= 0 then
return ngx.HTTP_FORBIDDEN
Expand Down Expand Up @@ -158,7 +158,9 @@ function _N:new(options)
-- global:optional:netaceaCaptchaPath
n.netaceaCaptchaPath = utils.normalizeRelativePath(utils.parseOption(options.netaceaCaptchaPath, nil))
-- global:optional:blockedResponseStatus
n.blockedResponseStatus = normalizeBlockedResponseStatus(utils.parseOption(options.blockedResponseStatus, nil))
n.blockedResponseStatus = normalizeHttpStatus(utils.parseOption(options.blockedResponseStatus, nil))
-- global:optional:challengeResponseStatus
n.challengeResponseStatus = normalizeHttpStatus(utils.parseOption(options.challengeResponseStatus, nil))
-- global:optional:blockedResponseBody
n.blockedResponseBody = utils.parseOption(options.blockedResponseBody, nil)
-- global:optional:blockedResponseContentType
Expand Down Expand Up @@ -364,7 +366,8 @@ function _N:mitigate()
enableCaptchaContentNegotiation = self.enableCaptchaContentNegotiation,
netaceaCaptchaPath = self.netaceaCaptchaPath,
captchaPath = self.netaceaCaptchaPath,
trackingId = trackingId
trackingId = trackingId,
challengeResponseStatus = self.challengeResponseStatus
})
end
return
Expand Down Expand Up @@ -414,7 +417,8 @@ function _N:mitigate()
serveCaptchaFailOpen(captchaBody, {
enableCaptchaContentNegotiation = self.enableCaptchaContentNegotiation,
netaceaCaptchaPath = self.netaceaCaptchaPath,
captchaPath = self.netaceaCaptchaPath
captchaPath = self.netaceaCaptchaPath,
challengeResponseStatus = self.challengeResponseStatus
})
return
end
Expand All @@ -427,7 +431,8 @@ function _N:mitigate()
serveCaptchaFailOpen(checkpointBody, {
enableCaptchaContentNegotiation = self.enableCaptchaContentNegotiation,
netaceaCaptchaPath = self.netaceaCaptchaPath,
captchaPath = self.netaceaCaptchaPath
captchaPath = self.netaceaCaptchaPath,
challengeResponseStatus = self.challengeResponseStatus
})
return
end
Expand Down
10 changes: 7 additions & 3 deletions src/lua_resty_netacea_mitigation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ end

function _M.serveCaptcha(captchaBody, options)
options = options or {}
ngx.status = ngx.HTTP_FORBIDDEN
local status = tonumber(options.challengeResponseStatus)
if not status or status < 100 or status > 599 or status % 1 ~= 0 then
status = ngx.HTTP_FORBIDDEN
end
ngx.status = status
if shouldServeCaptchaAsJson(options.enableCaptchaContentNegotiation, options.netaceaCaptchaPath) then
ngx.header["content-type"] = "application/json"
ngx.header["Cache-Control"] = "max-age=0, no-cache, no-store, must-revalidate"
Expand All @@ -111,13 +115,13 @@ function _M.serveCaptcha(captchaBody, options)
error("NETACEA CAPTCHA - missing trackingId for negotiated JSON response")
end
ngx.print(buildCaptchaJson(options.captchaPath, trackingId))
return ngx.exit(ngx.HTTP_OK)
return ngx.exit(status)
end

ngx.header["content-type"] = "text/html"
ngx.header["Cache-Control"] = "max-age=0, no-cache, no-store, must-revalidate"
ngx.print(captchaBody)
return ngx.exit(ngx.HTTP_OK)
return ngx.exit(status)
end

function _M.serveBlock(blockedResponseStatus, blockedResponseBody, blockedResponseContentType)
Expand Down
14 changes: 12 additions & 2 deletions test/lua_resty_netacea_mitigation_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@ describe("lua_resty_netacea_mitigation", function()

it("should exit with HTTP_OK", function()
mitigation.serveCaptcha("<html>captcha</html>")
assert.spy(ngx_mock.exit).was.called_with(200)
assert.spy(ngx_mock.exit).was.called_with(403)
end)

it("should use the configured challenge response status", function()
mitigation.serveCaptcha("<html>captcha</html>", {
challengeResponseStatus = 429
})
assert.are.equal(429, ngx_mock.status)
assert.spy(ngx_mock.exit).was.called_with(429)
end)

it("should serve json when html is not accepted but json is accepted", function()
Expand All @@ -73,13 +81,15 @@ describe("lua_resty_netacea_mitigation", function()
enableCaptchaContentNegotiation = true,
netaceaCaptchaPath = "/captcha",
captchaPath = "/getCaptcha",
trackingId = "a1a16640-e1f0-4de4-a3a6-140c45181383"
trackingId = "a1a16640-e1f0-4de4-a3a6-140c45181383",
challengeResponseStatus = 429
})

assert.are.equal("application/json", ngx_mock.header["content-type"])
assert.spy(ngx_mock.print).was.called_with(
'{"captchaRelativeURL":"/getCaptcha?trackingId=a1a16640-e1f0-4de4-a3a6-140c45181383","captchaAbsoluteURL":"https://example.com/getCaptcha?trackingId=a1a16640-e1f0-4de4-a3a6-140c45181383"}'
)
assert.spy(ngx_mock.exit).was.called_with(429)
end)

it("should keep html when text/html is accepted", function()
Expand Down
56 changes: 44 additions & 12 deletions test/lua_resty_netacea_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ insulate("lua_resty_netacea", function()
cookieEncryptionKey = options.cookieEncryptionKey,
secretKey = options.secretKey or "test-secret-key",
blockedResponseStatus = options.blockedResponseStatus,
challengeResponseStatus = options.challengeResponseStatus,
blockedResponseBody = options.blockedResponseBody,
blockedResponseContentType = options.blockedResponseContentType,
kinesisProperties = {
Expand Down Expand Up @@ -312,6 +313,25 @@ insulate("lua_resty_netacea", function()
assert.are.equal("text/plain; charset=utf-8", netacea.blockedResponseContentType)
end)

it("should store the configured challenge response status", function()
local netacea = Netacea:new({
ingestEnabled = true,
mitigationType = "MITIGATE",
mitigationEndpoint = "https://mitigation.example",
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
challengeResponseStatus = "429",
kinesisProperties = {
stream_name = "test-stream",
region = "eu-west-1",
aws_access_key = "test-access-key",
aws_secret_key = "test-secret-key"
}
})

assert.are.equal(429, netacea.challengeResponseStatus)
end)

it("should disable mitigation when mitigationType is INGEST", function()
local netacea = Netacea:new({
ingestEnabled = true,
Expand Down Expand Up @@ -591,7 +611,8 @@ insulate("lua_resty_netacea", function()
mitigationEndpoint = "https://mitigation.example",
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
netaceaCaptchaPath = "/captcha"
netaceaCaptchaPath = "/captcha",
challengeResponseStatus = 403
})

netacea:mitigate()
Expand All @@ -602,7 +623,8 @@ insulate("lua_resty_netacea", function()
enableCaptchaContentNegotiation = false,
netaceaCaptchaPath = "/captcha",
captchaPath = "/captcha",
trackingId = "e334cc64-6cc2-4193-92dd-237e38bab4a7"
trackingId = "e334cc64-6cc2-4193-92dd-237e38bab4a7",
challengeResponseStatus = 403
})
assert.are.equal("ip_flagged,captcha_serve", ngx_mock.ctx.NetaceaState.bc_type)
assert.spy(ngx_mock.exit).was_not_called()
Expand All @@ -627,7 +649,8 @@ insulate("lua_resty_netacea", function()
mitigationEndpoint = "https://mitigation.example",
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
enableCaptchaContentNegotiation = true
enableCaptchaContentNegotiation = true,
challengeResponseStatus = 403
})

mitigation_mock.getBestMitigation = spy.new(function()
Expand All @@ -637,7 +660,8 @@ insulate("lua_resty_netacea", function()

assert.spy(mitigation_mock.serveCaptcha).was.called_with("<html>captcha</html>", {
enableCaptchaContentNegotiation = true,
captchaPath = nil
captchaPath = nil,
challengeResponseStatus = 403
})
assert.spy(ngx_mock.exit).was_not_called()
end)
Expand All @@ -662,7 +686,8 @@ insulate("lua_resty_netacea", function()
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
enableCaptchaContentNegotiation = true,
netaceaCaptchaPath = "/captcha"
netaceaCaptchaPath = "/captcha",
challengeResponseStatus = 403
})

mitigation_mock.getBestMitigation = spy.new(function()
Expand All @@ -673,7 +698,8 @@ insulate("lua_resty_netacea", function()
assert.spy(mitigation_mock.serveCaptcha).was.called_with("<html>captcha</html>", {
enableCaptchaContentNegotiation = true,
netaceaCaptchaPath = "/captcha",
captchaPath = "/captcha"
captchaPath = "/captcha",
challengeResponseStatus = 403
})
assert.spy(ngx_mock.exit).was_not_called()
end)
Expand All @@ -698,7 +724,8 @@ insulate("lua_resty_netacea", function()
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
enableCaptchaContentNegotiation = true,
netaceaCaptchaPath = "/captcha"
netaceaCaptchaPath = "/captcha",
challengeResponseStatus = 403
})

mitigation_mock.getBestMitigation = spy.new(function()
Expand All @@ -709,7 +736,8 @@ insulate("lua_resty_netacea", function()
assert.spy(mitigation_mock.serveCaptcha).was.called_with('{"trackingId":"b0343c30-a382-42ad-9d65-fdb005fef054"}', {
enableCaptchaContentNegotiation = true,
netaceaCaptchaPath = "/captcha",
captchaPath = "/captcha"
captchaPath = "/captcha",
challengeResponseStatus = 403
})
assert.spy(ngx_mock.exit).was_not_called()
end)
Expand All @@ -733,7 +761,8 @@ insulate("lua_resty_netacea", function()
mitigationEndpoint = "https://mitigation.example",
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
enableCaptchaContentNegotiation = true
enableCaptchaContentNegotiation = true,
challengeResponseStatus = 403
})

mitigation_mock.getBestMitigation = spy.new(function()
Expand All @@ -744,7 +773,8 @@ insulate("lua_resty_netacea", function()
assert.spy(mitigation_mock.serveCaptcha).was.called_with("<html>captcha</html>", {
enableCaptchaContentNegotiation = true,
netaceaCaptchaPath = nil,
captchaPath = nil
captchaPath = nil,
challengeResponseStatus = 403
})
assert.spy(ngx_mock.exit).was_not_called()
end)
Expand All @@ -767,7 +797,8 @@ insulate("lua_resty_netacea", function()
mitigationEndpoint = "https://mitigation.example",
apiKey = "test-api-key",
cookieEncryptionKey = "test-cookie-encryption-key",
netaceaCaptchaPath = "/captcha"
netaceaCaptchaPath = "/captcha",
challengeResponseStatus = 403
})

netacea:mitigate()
Expand All @@ -777,7 +808,8 @@ insulate("lua_resty_netacea", function()
assert.spy(mitigation_mock.serveCaptcha).was.called_with("<html>captcha</html>", {
enableCaptchaContentNegotiation = false,
netaceaCaptchaPath = "/captcha",
captchaPath = "/captcha"
captchaPath = "/captcha",
challengeResponseStatus = 403
})
assert.spy(ngx_mock.exit).was_not_called()
end)
Expand Down