Don`t we need to care about thread-safety issue when we writing Lua script for openresty? - nginx

I`ve written a Lua script with openresty for testing the thread-safety issue,
The script as follow:
local _M = {}
local count = 0
function _M.increment()
count = count + 1
return count
end
return _M
I purposely use only 1 work process for testing, the nginx.conf as follow:
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
lua_package_path "/usr/local/openresty/nginx/?.lua;;";
server {
listen 80;
location / {
content_by_lua_block {
local counter = require "counter"
ngx.say(counter.increment())
}
}
}
}
Surprisingly, the value of counter always as same as the number of concurrent request I’ve sent?
My question is:
Some other language such as Golang,C#, if you increment the counter with multi thread without any lock, you would get a strange result finally, from my Lua experiment, it seems the counter always access by single thread exclusively? If so, when I used some data struct, like queue, does this mean I don`t need any lock when dequeue or enqueue an item (I just check the size of queue to ensure there is an item or a space for dequeue or enqueue)?

Related

Nginx block all traffic with specific custom header except to some urls

I have a service that is hosted in an internal network that is receiving traffic in port 443 (via https) behind a custom loadbalancer both from the internet but also from the internal network.
Internal network requests are coming with an extra custom header, let's call it X-my-lb-header.
I want to block all external incoming traffic to all uris (return an http response code), except to some specific ones.
Eg, let's say that i want to allow traffic that is coming to two endpoints /endpoind1/ (preffix match) and /endpoint2 actual match.
What is the best way to achieve a behaviour like this?
If my understanding is correct I need something like (not correct syntax bellow)
location = /endpoind2 {
if ($http_x_my_lb_header not exists) {
pass
} else {
return 404
}
... the rest of the directives
}
location ~ / {
if ($http_x_my_lb_header) {
return 404;
}
... the rest of the directives
}
But since else is not supported in nginx, i cannot figure out to do it.
Any ideas?
So you need some logic like
if (header exists) {
if (request URI isn't whitelisted) {
block the request
}
}
or in another words
if ((header exists) AND (request URI isn't whitelisted)) {
block the request
}
Well, nginx don't allow nested if blocks (nor logical conditions). While some people inventing a really weird but creative solutions like this one (emulating AND) or even this one (emulating OR), a huge part of such a problems can be solved using map blocks (an extremely powerfull nginx feature).
Here is an example:
# get the $block variable using 'X-my-lb-header' value
map $http_x_my_lb_header $block {
# if 'X-my-lb-header doesn't exists, get the value from another map block
'' $endpoint;
# default value (if the 'X-my-lb-header' exists) will be an empty string
# (unless not explicitly defined using 'default' keyword)
}
# get the $endpoint variable using request URI
map $uri $endpoint {
# endpoint1 prefix matching (using regex)
~^/endpoint1 ''; don't block
# endpoint2 exact matching
/endpoint2 ''; don't block
default 1; # block everything other
}
Now you can use this check in your server block (don't put it to some location, use at the server context):
if ($block) { return 404; }

Problem Segregating Original Request and Mirrored Request in nginx

I have 2 environments (envA, envB). envA needs to mirror its requests to envB as well as make 2 other calls to envB containing info from the response in envA. envA is not interested in the response of envB it's essentially a fire and forget situation. The objective, is to make sure that the operation and performance of envA is in no way affected by the calls made to envB. We have chosen to use nginx as our proxy and have it do the mirroring. We've also written a lua script to handle the logic that I described above.
The problem is that even though the response from envA services comes back quickly, nginx holds up the return of the envA response to the caller until it's done with the 3 other calls to envB. I want to get rid of that blockage somehow.
Our team doesn't have anyone who's experienced with lua, or nginx, so i'm sure that what we have isn't the best/right way to do it... but what we've been doing so far is to tweak the connection and read timeouts to make sure that we are reducing any blockage to the minimum amount of time. But this is just not getting us to where we want to be.
After doing some research i found https://github.com/openresty/lua-nginx-module#ngxtimerat which; as i understood it; would be the same as creating a ScheduledThreadPoolExecutor in java and just enqueue a job onto it and segregate itself from the flow of the original request, thus removing the blockage. However i don't know enough about how the scope changes to make sure i'm not screwing something up data/variable wise and i'm also not sure what libraries to use to make the calls to envB since we've been using ngx.location.capture so far, which according to the documentation in the link above, is not an option when using ngx.timer.at. So i would appreciate any insight on how to properly use ngx.timer.at or alternative approaches to accomplishing this goal.
This is the lua code that we're using. I've obfuscated it a great deal
but the bones of what we have is there, and the main part is the content_by_lua_block section
http {
upstream envA {
server {{getenv "ENVA_URL"}};
}
upstream envB {
server {{getenv "ENVB_URL"}};
}
server {
underscores_in_headers on;
aio threads=one;
listen 443 ssl;
ssl_certificate {{getenv "CERT"}};
ssl_certificate_key {{getenv "KEY"}};
location /{{getenv "ENDPOINT"}}/ {
content_by_lua_block {
ngx.req.set_header("x-original-uri", ngx.var.uri)
ngx.req.set_header("x-request-method", ngx.var.echo_request_method)
resp = ""
ngx.req.read_body()
if (ngx.var.echo_request_method == 'POST') then
local request = ngx.req.get_body_data()
resp = ngx.location.capture("/envA" .. ngx.var.request_uri, { method = ngx.HTTP_POST })
ngx.location.capture("/mirror/envB" .. ngx.var.uri, { method = ngx.HTTP_POST })
ngx.location.capture("/mirror/envB/req2" .. "/envB/req2", { method = ngx.HTTP_POST })
ngx.status = resp.status
ngx.header["Content-Type"] = 'application/json'
ngx.header["x-original-method"] = ngx.var.echo_request_method
ngx.header["x-original-uri"] = ngx.var.uri
ngx.print(resp.body)
ngx.location.capture("/mirror/envB/req3" .. "/envB/req3", { method = ngx.HTTP_POST, body = resp.body })
end
}
}
location /envA {
rewrite /envA(.*) $1 break;
proxy_pass https://envAUrl;
proxy_ssl_certificate {{getenv "CERT"}};
proxy_ssl_certificate_key {{getenv "KEY"}};
}
###############################
# ENV B URLS
###############################
location /envB/req1 {
rewrite /envB/req1(.*) $1 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
location /envB/req2 {
rewrite (.*) /envB/req2 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
location /envB/req3 {
rewrite (.*) /envB/req3 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
}
}
In terms of the problems we're seeing... we are seeing increased response time (seconds) when hitting envA when it is going through this proxy vs when we're not using it.
Pretty much five minutes after sending off the first answer I remembered that there's a proper way of doing this kind of cleanup activity.
The function ngx.timer.at allows you to schedule a function to run after a certain amount of time, including 0 for right after the current handler finishes. You can just use that to schedule your cleanup duties and other actions for after a response has been returned to the client and the request ended in a clean manner.
Here's an example:
content_by_lua_block {
ngx.say 'Hello World!'
ngx.timer.at(0, function(_, time)
local start = os.time()
while os.difftime(os.time(), start) < time do
end
os.execute('DISPLAY=:0 zenity --info --width 300 --height 100 --title "Openresty" --text "Done processing stuff :)"')
end, 3)
}
Note that I use zenity to show a popup window with the message since I didn't have anything set up to check if it really gets called.
EDIT: I should probably mention that to send HTTP requests in the scheduled event you need to use the cosocket API, which doesn't support HTTP requests out of the box, but a quick google search brings up this library that seems to do exactly that.
EDIT: It didn't take me long to find a better solution (see my other answer) but I'm leaving this one up as well because there might at the very least be some value in knowing this does technically work (and that you probably shouldn't be doing it this way)
The quickest thing I could come up with was this
content_by_lua_block {
ngx.say 'Hello World!'
local start = os.time()
ngx.flush()
ngx.req.socket:close()
while os.difftime(os.time(), start) < 4 do
end
}
First flush the actual output to the client with ngx.flush(), then just close the connection with ngx.req.socket:close(). Pretty sure this isn't the cleanest option, but for the most part it works. I'll post another answer if I can find a better solution though :)

Run an access_by_lua before an if

I'm trying to get some text from an api then proxy pass if it equals to something. After some testing, I discovered that the access_by_lua is getting executed after the if statement.
Here's my current code:
set $protectione 'disabled';
access_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("http://127.0.0.1/ddos/fw.json", { method = "GET" })
ngx.var.protectione = res.body
}
if ( $protectione = 'disabled' ) {
proxy_pass http://backend;
set $allowreq 1;
}
Is there an alternative to my problem ?
You definitely should take a look at next tutorial and this post
You don't get the idea of nginx request processing phases.
Nginx directives are not executed sequentaly.
if and set directives work on rewrite phase which is processed before access phase.

ngx_http_limit_req module can not limit request correct

I want to protect my nginx server under a safe concurrency request, so I had set the nginx server(nginx 1.6.2) limitted on 30000 qps by config like this:
limit_req_zone $server_port zone=perport:10m rate=30000r/s;
limit_req_status 597;
limit_req zone=perport burst=0 nodelay;
But the nginx server have just 6000 qps, I had got many limitted error.
I had tried to read the source code for the reason, and the I found this (source link):
379: tp = ngx_timeofday();
380: now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
...
409: ms = (ngx_msec_int_t) (now - lr->last);
410:
411: excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
...
419: if ((ngx_uint_t) excess > limit->burst) {
420: return NGX_BUSY;
...
I think there has a bug:
When the ngx_abs(ms) got 0, the rate value can not affect excess whatever it set to be.
When two request had same hash key and arrive at same time (same millisecond).
The excess will be 1000
The request would be NGX_BUSY when no burst set.
Cause I used the server_port as the hash key, or whatever other key which would take concurrency request,whatever I set the rate to 30000r/s,3000000r/s or 3000000000r/s, it make no sence.
I read the latest nginx(1.11.6) code, looks like no changes.

How to block referral spam using Nginx?

I'm running two mongrels under an Nginx server. I keep getting requests for a nonexistent file. The IP addresses change frequently but the referring URL stays the same. I'd like to resolve this.
https://calomel.org/nginx.html
Block most "referrer spam" -- "more of an annoyance than a problem"
nginx.conf
## Deny certain Referers (case insensitive)
## The ~* makes it case insensitive as opposed to just a ~
if ($http_referer ~* (babes|click|diamond|forsale|girl|jewelry|love|nudit|organic|poker|porn|poweroversoftware|sex|teen|video|webcam|zippo))
{ return 403; }
Using Nginx map module is a a bit more efficient and easier to manage as the list gets long.
Put this in your http {} block :
map $http_referer $bad_referer {
hostnames;
default 0;
# Put regexes for undesired referers here
"~social-buttons.com" 1;
"~semalt.com" 1;
"~kambasoft.com" 1;
"~savetubevideo.com" 1;
"~descargar-musica-gratis.net" 1;
"~7makemoneyonline.com" 1;
"~baixar-musicas-gratis.com" 1;
"~iloveitaly.com" 1;
"~ilovevitaly.ru" 1;
"~fbdownloader.com" 1;
"~econom.co" 1;
"~buttons-for-website.com" 1;
"~buttons-for-your-website.com" 1;
"~srecorder.co" 1;
"~darodar.com" 1;
"~priceg.com" 1;
"~blackhatworth.com" 1;
"~adviceforum.info" 1;
"~hulfingtonpost.com" 1;
"~best-seo-solution.com" 1;
"~googlsucks.com" 1;
"~theguardlan.com" 1;
"~i-x.wiki" 1;
"~buy-cheap-online.info" 1;
"~Get-Free-Traffic-Now.com" 1;
}
Put this in your server {} block:
if ($bad_referer) {
return 444; # emtpy response
}
It worked for me.
Got this from http://fadeit.dk/blog/post/nginx-referer-spam-blacklist
I've been in a similar situation before where I needed to block people based on behaviour instead of other arbitrary rules that a firewall could sort out on its own.
They way I worked around the problem was to make my logic (Rails in your case) do the blocking... But a long way round:
Have your logic maintain a block-list as a new-line separated plaintext file.
Create a bash (or other) script as root to read this file and add its listees to your firewall's blocklist
Create a cron job to call the script, again, as root
The reason I do it this way around (rather than just giving Django permissions to alter firewall config) is simply: security. If my application were hacked, I wouldn't want it to hurt anything else.
The bash script is something like this:
exec < /path/to/my/djago-maintained/block-list
while read line
do
iptables -A INPUT --source $line/32 -j DROP
done
I have created module for checking incoming IP in black lists https://github.com/oneumyvakin/ngx_http_blacklist_lookup_module
it's uses blacklists from projecthoneypot.org, blocklist.de and uceprotect.net

Resources