Extend NGINX with Lua — DDOS Mitigation using Cookie validation
Tags: openresty, nginx, lua, devops
Published: 2019-08-04 17:26:21

---
title: Extend NGINX with Lua — DDOS Mitigation using Cookie validation
published: true
tags: openresty,nginx,lua,devops
canonical_url: 
---

### Introduction

“OpenResty® is a full-fledged web platform that integrates our enhanced version of the [Nginx](https://openresty.org/en/nginx.html) core, our enhanced version of [LuaJIT](https://github.com/openresty/luajit2), many carefully written Lua libraries, lots of high quality [3rd-party Nginx modules](https://openresty.org/en/components.html), 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

![](https://cdn-images-1.medium.com/max/1005/1*gfAT3GULg89r8MivUVCjQg.jpeg)<figcaption>Different phases of a request lifecycle in OpenResty</figcaption>

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](https://www.digitalocean.com/community/tutorials/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!')
         }
    }
}
```

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](https://github.com/cloudflare/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
```

#### 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](https://github.com/SkyLothar/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)
```

#### 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
```

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:

{% gist https://gist.github.com/satrobit/6a22d3475a98ad11fbf6f79388370650 %}

#### LuaRocks

our .rockspec file will be something like this:

{% gist https://gist.github.com/satrobit/026312ee2e48a4c23a495102ce27b4ed %}

#### Run!

You can run the module like this:

```
rewrite_by_lua_block {
    local ddos = require("resty.ddos")
    ddos.run("some-key")
}
```

I’ve uploaded the code to GitHub. [Click here to see lua-resty-ddos](https://github.com/satrobit/lua-resty-ddos).

I encourage you to read the [lua-nginx-module README](https://github.com/openresty/lua-nginx-module) since it has almost everything you need to know about OpenResty.

I hope you enjoyed the article.