DEV Community

Amir Keshavarz
Amir Keshavarz

Posted on • Originally published at Medium on

Extend NGINX with Lua — DDOS Mitigation using Cookie validation

Introduction

“OpenResty® is a full-fledged web platform that integrates our enhanced version of the Nginx core, our enhanced version of LuaJIT, many carefully written Lua libraries, lots of high quality 3rd-party Nginx modules, and most of their external dependencies. It is designed to help developers easily build scalable web applications, web services, and dynamic web gateways.”

Challenge

One of the challenges facing web server operators is to detect bots from real users. One of which is to send a token as a cookie and except the client to return the cookie.

Solution

This can be done very easily using the OpenResty utilities. Let's start from the basics. As you may know, NGINX has a chained module architecture. There are lots of phases in the lifecycle of a request. OpenResty provides access to these phases so we can change how NGINX behaves using the beloved Lua.

Phases

Different phases of a request lifecycle in OpenResty

We are going to operate at rewrite_by_lua phase.

OpenResty Basics

To get a fair understanding of how to use OpenResty I’d refer you to “How to Use the OpenResty Web Framework for Nginx on Ubuntu 16.04” which is a really good article for beginners.

OpenResty defines multiple directives in the NGINX config file so we can put our Lua code in it. Look at this for example:

server {
    . . .

    location /example {
         default_type 'text/plain';

         content_by_lua_block {
             ngx.say('Hello, Sammy!')
         }
    }
}
Enter fullscreen mode Exit fullscreen mode

The content_by_lua basically lets us manipulate the content returned to the client. Since we don’t need to provide any content We will use the rewrite_by_lua directive which gives us access to the rewrite phase.

Okay, Let’s get back to our module which is a simple cookie validation for ddos mitigation.

Module Flow

Cookie Lookup

The flow of our module will be kept simple and stupid. The first thing we do is to check for a cookie named “Ref”. For cookie manipulation, I’ve used a lib named lua-resty-cookie.

To get a cookie we simply make a cookie object call the get method:

local cookie, err = ck:new()
if not cookie then
    ngx.log(ngx.ERR, err)
    return
end
local Ref_field, err = cookie:get("Ref")
if not Ref_field then
    -- No cookie for you
end
Enter fullscreen mode Exit fullscreen mode

Return JWT

If the cookie doesn’t exist we make a token using JWT and include the client’s IP Address in the payload, then return a HTTP code 302 with the current URL and the cookie included.

For JWT, I used the lua-resty-jwt. You see that we basically create a token, put in a cookie and send it back to the client:

-- Make JWT
local jwt_token = jwt:sign(key,
{
    header={typ="JWT", alg="HS256"},
    payload={remote_addr=ngx.var.remote_addr}
})
-- set cookie
local ok, err = cookie:set({key = "Ref", value = jwt_token})
if not ok then
    ngx.log(ngx.ERR, err)
    return
end
-- Return to the same page with the Ref cookie included
return ngx.redirect(ngx.var.request_uri)
Enter fullscreen mode Exit fullscreen mode

JWT Verification

When the 302 hits the client, another request comes in and this time the “Ref” cookie is also included in the request. Now that we have the cookie the scenario will be the same as if the client had the cookie all along.

Now we try to verify the token like this:

-- Verify the Ref
local jwt_obj = jwt:verify(key, Ref_field)
if jwt_obj.verified ~= true or jwt_obj.payload.remote_addr ~= ngx.var.remote_addr then
    -- Create token and return it
end
Enter fullscreen mode Exit fullscreen mode

You can see that if the token fails to verify we can repeat the steps like the client has no token.

Lua Module

Now we wrap our codes in a table with some metadata and return the table.

This is our entire code:

LuaRocks

our .rockspec file will be something like this:

Run!

You can run the module like this:

rewrite_by_lua_block {
    local ddos = require("resty.ddos")
    ddos.run("some-key")
}
Enter fullscreen mode Exit fullscreen mode

I’ve uploaded the code to GitHub. Click here to see lua-resty-ddos.

I encourage you to read the lua-nginx-module README since it has almost everything you need to know about OpenResty.

I hope you enjoyed the article.

Top comments (0)