Programmable reverse proxy

Zogoo
5 min readJul 28, 2021

I have been searching something called in my mind “programmable reverse proxy” solution.

The reason that when you need to create feature which uses low level technology with dynamic business logic such as client certificate authentication, IP restriction. Of course, there are lots of possible solutions solve this kind of task. Like, Implement your service behind of existing reverse proxy and pass all values from low level layers.

But, I wanted to solve it with clean way, like validate it on a low level layer.

You might ask why? I will explain here.

The reverse proxy and dynamic plugin

When your application has logics who handle independent organization like company, store, school. Your application will definitely depend on each organization (tenant) data. And your feature should work dynamically works based on that organization’s (tenant) data. Sounds like too much abstract right? Let me give you a more precise example.

There are lots of existing projects for reverse proxy such as Nginx, Apache. I will choose Nginx because, I like the features and configuration style.

Example 1
Let’s say each tenant has their own sub domain and also their own SSL certificate for their domain. Like follows.
https://toyota.trustlogin.com for Toyota specific service with Toyota policies
https://honda.trustlogin.com for Honda specific service with Honda policies
Toyota wants to use own SSL certificate for their domain.

Example 2
Let’s say a tenant called “Globalsign” wants to use client certificate authentication with their own private root certificate. Another company called “Pepabo” wants also wants to use client certificate, but this time they want to use the public root certificate which provided by default.

Example 3
Some tenants want to block requests with a white list with their IP addresses, but some tenants want to use a blacklist for their request for their specific sub domains.

Example 4
Some companies block all requests except HTTP request that contains specific JWT token or Kerberos ticket in the HTTP header.

All those examples require that validation of request based on each tenant information. Of course, this could be solved with the higher level web application. But the question is that “is it efficient and secure enough”.
You know that reading low level layer data (client certificate, TLS certificate, IP address from TCP connection) from web application that run behind reverse proxy (Nginx) is complicated and require extra implementation for validations.
For example. Validation of client certificate (mutual authentication) with tenant specific logic (my previous article from here) is complicated and slow.

NGINX reverse proxy with MRuby plugin

But there is a solution that you can solve tenant specific low layer validation feature with lower layer connection with balanced performance and complication.

You may want to ask how balanced it, is here is the some graph for that

More info from here: https://github.com/matsumotory/ngx_mruby

Nginx team started officially supporting dynamic module from “nginx-1.9.11” version this allows for us load dynamic module (.so file) with “nginx.conf” file and to do anything we want to with a low layer (TCP) connection data.

How you create your own plugin with this

  1. Just include pre-built dynamic module file (ngx_http_mruby_module.so) to your Nginx module folder and add the following line to your Nginx config file.
load_module modules/ngx_http_mruby_module.so;

2. Write your script with MRuby and put it some where that Nginx could access it.
For example, Small script that blocks IP addresses.

conn = Nginx::Connection.new
request = Nginx::Request.new
remote_ip = conn.remote_ip
uri_array = request.uri.split('/')
Nginx.errlogger Nginx::LOG_NOTICE, "remote ip: #{remote_ip}"
# Get tenant name from URL, https://www.globalsign.com/toyota
tenant_name = request.uri.split('/')[2]
r = Redis.new "192.168.12.251", 6379
white_list = r.get (tenant_name)
# Check request IP address the whitelist
return Nginx::HTTP_FORBIDDEN unless !white_list.empty? || white_list.include?(remote_ip)
# OK
Nginx.echo 'Welcome to Toyota group service'
Nginx::HTTP_OK

3. Add the following line into your “nginx.conf” file.

location /ip {
mruby_content_handler /usr/local/nginx/ruby_plugin/ip_restriction.rb cache;
}

That’s all.

Conclusion

Earlier time, writing Nginx (or Apache) plugin with C language requires a lot of time to implement and it was complicated. Because you need to learn Nginx or Apache way to implementation of C libraries, they use specific data structure and specific memory management features in their code.
And you need to compile your C code with Nginx. This was a painful long task.

Those issues are resolved by Nginx MRuby plugin you just need to focus on your high level script for your business logic. And it’s uses Ruby language which is very easy to learn and write code.

Probably, you still have questions, what is the benefit of writing a low level plugin. Here is the answer.

Blocking unwanted request in early as possible before reaching your actual web application will helps in following.
1. Security: Most web frameworks are using open source libraries to get request and process before pass to your business logic (controller) and in there if you do security logics it might be too late if open source libraries has vulnerabilities.
2. Cost: Web application services mostly designed to process a request with minimum possible cost. For this reason automatic scale in out feature they use and it triggers based on request. If you took lots of unwanted requests in your web application you need to spend a lot of money for that. Above example, if you check IP address in controller level and you have huge requests that will consume more resources from your server.

Direct access to low layer data will helps that write efficient feature. HTTP is very high level layer and the data itself is very insecure and over writable. For this reason TLS is used for all web services, but TLS handshake process will happen before data transfer to the HTTP protocol. This has mean you cannot easily access to TCP and TLS data from your web controller, especially if you are using open source frameworks or running your web service behind reverse proxy or some security services. But Nginx plugin allows to access any low level data, including SSL key. In other word you can talk directly user client browser.

The drawback of this solution you have limited libraries and functionalities for your script. If you want to more feature, you need to contribute to the Nginx Mruby project or maybe MRuby projects.

But I think this very rare scenario, because you might doing the wrong thing like putting high level feature as Nginx plugin. So, you need to think about which feature could be in Nginx and which one should be in your web application.

--

--