Empowering Nginx with Lua code

Attention: I released resty-dynacode an openresty library enabling users to add Lua code dynamically to Nginx.

This is the second post in the series where we develop an edge computing platform. In this post, we’ll add some code/behavior to the front end servers. Here’s a link to the previous entry.

Add code inside the front end

The OTT service we did before don’t employ any kind of authentication thus the users can watch the streams for free. To solve this authentication issue we can add Lua code embed into nginx.

OpenResty – an Nginx with support for LuaJIT 2.0/2.1 code.

To run Lua code inside nginx you need to understand a little bit of the request phases within the server. The request will travel across different stages where you can intercept it using Nginx directives and add the code.

Screen Shot 2020-04-20 at 2.09.49 PM

Just for the sake of learning, the authentication logic will be a straightforward token system. During the access phase, we’ll deny access for those with no proper authentication. Once a user has the required token it’s going to be persisted in form of a cookie.

Fixed token with no expiration is unsafe for production usage, you should look for something like JWT.

server {
location / {
proxy_cache my_cache;
proxy_cache_lock on;
proxy_pass http://backend;
access_by_lua_block {
local token = ngx.var.arg_token or ngx.var.cookie_superstition
if token ~= "token" then
return ngx.exit(ngx.HTTP_FORBIDDEN)
else
ngx.header['Set-Cookie'] = {'superstition=token'}
end
}
}
}
view raw nginx.conf hosted with ❤ by GitHub

The edge server can run useful behavior/code, now let’s laid out some examples that demonstrate the power we can have while executing functions at the front end.

Suppose a hacker, behing the IP 192.168.0.253, is exploting a known issue, that is going to be fixed soon. We can solve that by forbiddening his/her IP. Adding lua code, to the same phase, can fix this problem.

You can access all the nginx variables using the api ngx.var.VARIABLE.

if ngx.var.remote_addr == "192.168.0.253" then
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
view raw acl.lua hosted with ❤ by GitHub

Nginx has the deny directive to solve this problem although it doesn’t allow a dynamic way to update the IP list. We would need to reload the server every time we want to update the IPs.

It’s wanted to avoid different domains to consume our streams, to prevent that, we’re going to examine the referer header and reject all the requests not originated from our domain.

CORS and CSP will be safer to solve this issue.

local headers = ngx.req.get_headers()
if not string.find(headers["Referer"],"localhost") then
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
view raw Origin.lua hosted with ❤ by GitHub

To on-the-fly change the response from the backend, we’ll add a custom HLS tag in the playlist.

header_filter_by_lua_block
since we're going to change the content
we need to
ngx.header.content_length = nil
body_filter_by_lua_block
ngx.arg[1] = ngx.arg[1] .. "\n#COPYRIGHT: mysite.com"
view raw changing.lua hosted with ❤ by GitHub

To decorate the HTTP headers, we’ll attach new ones exposing some metrics from the server and for that matter, it can rely on the ngx.header[‘name’] API.

header_filter_by_lua_block
ngx.header['X-Metrics-upstream_response_time'] = ngx.var.upstream_response_time
ngx.header['X-Metrics-upstream_connect_time'] = ngx.var.upstream_connect_time
ngx.header['X-Metrics-request_time'] = ngx.var.request_time
ngx.header['X-Metrics-tcpinfo_rtt'] = ngx.var.tcpinfo_rtt
ngx.header['X-Metrics-time_iso8601'] = ngx.var.time_iso8601
view raw gistfile1.lua hosted with ❤ by GitHub

Finally, we’ll count how many requests a given user (based on her/his IP) does and expose it through a custom HTTP header. The counter was stored in Redis.

counting how many requests a given ip did
local redis_client = redis_cluster:new(config)
local resp, err = redis_client:incr(ngx.var.remote_addr)
ngx.header['X-Request-Counter'] = resp

All this is working, if you want, you can test it by yourself.

# make sure you have docker
git clone https://github.com/leandromoreira/nott.git
cd nott
git checkout 1.0.2
# in a tab
make run
# wait until the platform is up and running
# and in another tab run
make broadcast_tvshow
# ^ for linux users you use –network=host and your
# IP instead of this docker.for.mac.host.internal
# for windows user I dunno =(
# but you can use OBS and point to your own machine
# open your browser and point it to http://localhost:8080/app
# observe the metrics in the network console window
# or even our custom hls manifest
# try to play our stream in clappr.io/demo/ it won't do it.
view raw steps1.sh hosted with ❤ by GitHub

Conclusion

Did you notice a pattern? Every time we want add a new feature, we need to:

  • write a little bit of Lua code
  • attach it to a request phase directive in nginx
  • deploy and reload the edge servers

That’s why we need to build an edge computing platform, to put code faster into production and avoid server reload.

5 thoughts on “Empowering Nginx with Lua code

Leave a Reply to Building an open-source OTT platform | Leandro Moreira Cancel reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s