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.0-0.rockspec ./
COPY ./lua_resty_netacea-1.2.0-0.rockspec ./
COPY ./src ./src
RUN /usr/local/openresty/luajit/bin/luarocks make ./lua_resty_netacea-1.0-0.rockspec
RUN /usr/local/openresty/luajit/bin/luarocks make ./lua_resty_netacea-1.2.0-0.rockspec

FROM build AS test

Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,60 @@ With coverage report (sent to stdout) `docker compose run -e LUACOV_REPORT=1 --b

## Configuration

### nginx.conf - ingest only

Use ingest-only mode when you want to send request data to the ingest pipeline without calling the Mitigation Endpoint.

Set `ingestEnabled` to `true`, set `mitigationEnabled` to `false`, and leave `mitigationType` empty.

`kinesisProperties` must be provided for ingest to remain enabled.

```conf
worker_processes 1;

events {
worker_connections 1024;
}

http {
lua_package_path "/usr/local/share/lua/5.1/?.lua;;";
lua_max_running_timers 2048;
lua_max_pending_timers 4096;
lua_socket_pool_size 1024;
lua_need_request_body on;
resolver 8.8.8.8 ipv6=off;
lua_ssl_verify_depth 2;
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
init_worker_by_lua_block {
netacea = (require 'lua_resty_netacea'):new({
apiKey = 'your-api-key',
realIpHeader = 'realip-header',
ingestEnabled = true,
mitigationEnabled = false,
mitigationType = '',
kinesisProperties = {
stream_name = 'your-kinesis-stream',
region = 'eu-west-1',
aws_access_key = 'your-aws-access-key',
aws_secret_key = 'your-aws-secret-key'
}
})
}
log_by_lua_block {
netacea:ingest()
}

server {
listen 80;
server_name localhost;
location / {
default_type text/html;
content_by_lua 'ngx.say("<p>hello, world</p>")';
}
}
}
```

### nginx.conf - mitigate

```conf
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package = "lua_resty_netacea"
version = "1.0-0"
version = "1.2.0-0"
source = {
url = "git://github.com/Netacea/lua_resty_netacea",
branch = "master"
Expand Down
52 changes: 44 additions & 8 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.1.0'
_N._VERSION = '1.2.0'
_N._TYPE = 'nginx'

local ngx = require 'ngx'
Expand Down Expand Up @@ -57,6 +57,7 @@ function _N:new(options)
end
-- mitigate:required:secretKey
n.secretKey = b64.decode_base64url(options.secretKey) or ''
n.sessionEnabled = n.secretKey and n.secretKey ~= ''
if not n.secretKey or n.secretKey == '' then
n.mitigationEnabled = false
end
Expand Down Expand Up @@ -118,11 +119,15 @@ end
function _N:ingest()
ngx.log(ngx.DEBUG, "NETACEA INGEST - in netacea:ingest(): ", self.ingestEnabled)
if not self.ingestEnabled then return nil end
ngx.ctx.NetaceaState.bc_type = self:setBcType(
tostring(ngx.ctx.NetaceaState.protector_result.match or Constants['idTypes'].NONE),
tostring(ngx.ctx.NetaceaState.protector_result.mitigate or Constants['mitigationTypes'].NONE),
tostring(ngx.ctx.NetaceaState.protector_result.captcha or Constants['captchaStates'].NONE)
)
local NetaceaState = ngx.ctx.NetaceaState
local protector_result = NetaceaState and NetaceaState.protector_result
if protector_result then
NetaceaState.bc_type = self:setBcType(
tostring(protector_result.match or Constants['idTypes'].NONE),
tostring(protector_result.mitigate or Constants['mitigationTypes'].NONE),
tostring(protector_result.captcha or Constants['captchaStates'].NONE)
)
end
return self.ingestPipeline:ingest()
end

Expand All @@ -133,6 +138,7 @@ function _N:handleSession()

-- Check cookie
local cookie = ngx.var['cookie_' .. self.cookieName] or ''
ngx.ctx.mitata = cookie
local parsed_cookie = netacea_cookies.parseMitataCookie(cookie, self.secretKey)
ngx.log(ngx.DEBUG, "NETACEA MITIGATE - parsed cookie: ", cjson.encode(parsed_cookie))
if parsed_cookie.user_id then
Expand All @@ -149,7 +155,11 @@ function _N:handleSession()
end

function _N:refreshSession(reason)
local protector_result = ngx.ctx.NetaceaState.protector_result
local protector_result = ngx.ctx.NetaceaState.protector_result or {
match = Constants['idTypes'].NONE,
mitigate = Constants['mitigationTypes'].NONE,
captcha = Constants['captchaStates'].NONE
}

local grace_period = ngx.ctx.NetaceaState.grace_period or 60

Expand All @@ -169,6 +179,7 @@ function _N:refreshSession(reason)
local cookies = {
self.cookieName .. '=' .. new_cookie.mitata_jwe .. ';' .. self.cookieAttributes
}
ngx.ctx.mitata = new_cookie.mitata_jwe

if protector_result.captcha_cookie and protector_result.captcha_cookie ~= '' then
local captcha_cookie_encrypted = netacea_cookies.encrypt(self.secretKey, protector_result.captcha_cookie)
Expand All @@ -194,8 +205,33 @@ function _N:handleCaptcha()
end


function _N:refreshIngestSession()
local parsed_cookie = self:handleSession()

if parsed_cookie.valid then
ngx.ctx.NetaceaState.protector_result = {
match = parsed_cookie.data.mat,
mitigate = parsed_cookie.data.mit,
captcha = parsed_cookie.data.cap
}
return parsed_cookie
end

if not ngx.ctx.NetaceaState.UserId then
ngx.ctx.NetaceaState.UserId = netacea_cookies.newUserId()
end

self:refreshSession(parsed_cookie.reason)
return parsed_cookie
end

function _N:mitigate()
if not self.mitigationEnabled then return nil end
if not self.mitigationEnabled then
if self.sessionEnabled then
return self:refreshIngestSession()
end
return nil
end
local parsed_cookie = self:handleSession()

if not parsed_cookie.valid then
Expand Down
11 changes: 9 additions & 2 deletions src/lua_resty_netacea_cookies_v3.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ NetaceaCookies.__index = NetaceaCookies

function NetaceaCookies.decrypt(secretKey, value)
local decoded = jwt:verify(secretKey, value)
if not decoded.verified then
if not decoded or not decoded.verified then
return nil
end
return decoded.payload
Expand Down Expand Up @@ -82,6 +82,13 @@ function NetaceaCookies.parseMitataCookie(cookie, secretKey)
end

local decoded_str = NetaceaCookies.decrypt(secretKey, cookie)
if not decoded_str then
return {
valid = false,
reason = constants['issueReasons'].INVALID_SESSION
}
end

local decoded = ngx.decode_args(decoded_str)
if not decoded then
return {
Expand Down Expand Up @@ -142,4 +149,4 @@ function NetaceaCookies.parseMitataCookie(cookie, secretKey)
}
end

return NetaceaCookies
return NetaceaCookies
17 changes: 16 additions & 1 deletion test/lua_resty_netacea_cookies_v3_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe("lua_resty_netacea_cookies_v3", function()
return nil
end
if not str then
return nil
error("bad argument #1 to 'decode_args' (string expected, got nil)")
end
local result = {}
for pair in str:gmatch("[^&]+") do
Expand Down Expand Up @@ -560,6 +560,7 @@ describe("lua_resty_netacea_cookies_v3", function()
assert.is_false(result.valid)
assert.is.equal('invalid_session', result.reason)
assert.spy(jwt_mock.verify).was.called_with(match.is_not_nil(), "secret_key", "invalid_token")
assert.spy(ngx_mock.decode_args).was_not_called()
end)

it("should return invalid result for invalid payload format", function()
Expand Down Expand Up @@ -792,6 +793,20 @@ describe("lua_resty_netacea_cookies_v3", function()
assert.spy(jwt_mock.verify).was.called_with(match.is_not_nil(), "secret_key", "invalid_token")
end)

it("should return nil when JWT decryption fails without a decoded token", function()
jwt_mock.verify = spy.new(function()
return nil
end)

local ok, result = pcall(function()
return NetaceaCookies.decrypt("secret_key", "invalid_token")
end)

assert.is_true(ok)
assert.is_nil(result)
assert.spy(jwt_mock.verify).was.called_with(match.is_not_nil(), "secret_key", "invalid_token")
end)

it("should return nil for unverified JWT token", function()
jwt_mock.verify = spy.new(function(self, secretKey, token)
return { verified = false }
Expand Down
Loading