Skip to content

Instantly share code, notes, and snippets.

@karmi
Last active September 7, 2024 20:44
Show Gist options
  • Save karmi/b0a9b4c111ed3023a52d to your computer and use it in GitHub Desktop.
Save karmi/b0a9b4c111ed3023a52d to your computer and use it in GitHub Desktop.
Example Nginx configurations for Elasticsearch (https://www.elastic.co/blog/playing-http-tricks-nginx)
nginx/
!nginx/.gitkeep
!nginx/logs/.gitkeep
src/
tmp/

Example Nginx Configurations for Elasticsearch

This repository contains couple of example configurations for using Nginx as a proxy for Elasticsearch.

These examples can be run standalone from this repository -- the general pattern is:

$ nginx -p $PWD/nginx/ -c $PWD/<CONFIG FILE>

When you change the configuration, simply reload the Nginx process to pick up the changes:

$ nginx -p $PWD/nginx/ -c $PWD/<CONFIG FILE> -s reload

Please refer to the Nginx documentation for more information.

nginx_round_robin.conf

A simple proxy which distributes requests in a round-robin way across configured nodes.

More information: http://nginx.org/en/docs/http/ngx_http_upstream_module.html

nginx_keep_alive.conf

Configures the proxy to keep a pool of persistent connections, preventing opening sockets at Elasticsearch for each connection, e.g. with deficient HTTP clients.

More information: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive

nginx_http_auth_basic.conf

The simplest possible authorization proxy for Elasticsearch: allow access only to users authenticated with HTTP Basic Auth, with credentials stored in a passwords file.

nginx_http_auth_deny_path.conf

A variation on the simple authorization proxy, which prevents access to certain URLs (_shutdown).

nginx_http_auth_allow_path_and_method.conf

A variation on the authorization proxy, which uses named locations to allow certain paths and methods without authorization.

Demonstrates how to use error codes in Nginx configuration to route requests and how to work around the lack of multiple conditions in Nginx' if statement.

More information: http://wiki.nginx.org/RewriteMultiCondExample

nginx_http_auth_roles.conf

Demonstrates how to use multiple Nginx servers to separate access rights for multiple types of users: unauthenticated, users and admins.

Unauthenticated users can access HEAD /, but nothing else.

Authenticated user can access only the _search and _analyze endpoints (with whatever HTTP method), other endpoints are denied.

More information: http://nginx.org/en/docs/http/ngx_http_core_module.html#location

nginx_authorize_by_lua.conf

Demonstrates how to use custom logic for implementing authorization, via the Lua support in Nginx.

The request is authenticated against credentials in the passwords file and if allowed by the access_by_lua_file return value, proxied to Elasticsearch.

The authorization logic is stored in the authorize.lua file, which contains a simple "dictionary" (in the form of Lua table) with rules for three "roles": anybody, users and admins.

Based on the $remote_user Nginx variable value, the request path and method are evaluated against the dictionary, and the request is denied with "403 Forbidden" if no matching rule is found.

Lua and Nginx Overview: http://www.londonlua.org/scripting_nginx_with_lua/slides.html

More information: http://openresty.org

admin:bHw.s5hN/IvE6
--[[
Provides Elasticserach endpoint authorization based on rules in Lua and authenticated user
See the `nginx_authorize_by_lua.conf` for the Nginx config.
Synopsis:
$ /usr/local/openresty/nginx/sbin/nginx -p $PWD/nginx/ -c $PWD/nginx_authorize_by_lua.conf
$ curl -i -X HEAD 'http://localhost:8080'
HTTP/1.1 401 Unauthorized
curl -i -X HEAD 'http://all:all@localhost:8080'
HTTP/1.1 200 OK
curl -i -X GET 'http://all:all@localhost:8080'
HTTP/1.1 403 Forbidden
curl -i -X GET 'http://user:user@localhost:8080'
HTTP/1.1 200 OK
curl -i -X GET 'http://user:user@localhost:8080/_search'
HTTP/1.1 200 OK
curl -i -X POST 'http://user:user@localhost:8080/_search'
HTTP/1.1 200 OK
curl -i -X GET 'http://user:user@localhost:8080/_aliases'
HTTP/1.1 200 OK
curl -i -X POST 'http://user:user@localhost:8080/_aliases'
HTTP/1.1 403 Forbidden
curl -i -X POST 'http://user:user@localhost:8080/myindex/mytype/1' -d '{"title" : "Test"}'
HTTP/1.1 403 Forbidden
curl -i -X DELETE 'http://user:user@localhost:8080/myindex/'
HTTP/1.1 403 Forbidden
curl -i -X POST 'http://admin:admin@localhost:8080/myindex/mytype/1' -d '{"title" : "Test"}'
HTTP/1.1 200 OK
curl -i -X DELETE 'http://admin:admin@localhost:8080/myindex/mytype/1'
HTTP/1.1 200 OK
curl -i -X DELETE 'http://admin:admin@localhost:8080/myindex/'
HTTP/1.1 200 OK
]]--
-- authorization rules
local restrictions = {
all = {
["^/$"] = { "HEAD" }
},
user = {
["^/$"] = { "GET" },
["^/?[^/]*/?[^/]*/_search"] = { "GET", "POST" },
["^/?[^/]*/?[^/]*/_msearch"] = { "GET", "POST" },
["^/?[^/]*/?[^/]*/_validate/query"] = { "GET", "POST" },
["/_aliases"] = { "GET" },
["/_cluster.*"] = { "GET" }
},
admin = {
["^/?[^/]*/?[^/]*/_bulk"] = { "GET", "POST" },
["^/?[^/]*/?[^/]*/_refresh"] = { "GET", "POST" },
["^/?[^/]*/?[^/]*/?[^/]*/_create"] = { "GET", "POST" },
["^/?[^/]*/?[^/]*/?[^/]*/_update"] = { "GET", "POST" },
["^/?[^/]*/?[^/]*/?.*"] = { "GET", "POST", "PUT", "DELETE" },
["^/?[^/]*/?[^/]*$"] = { "GET", "POST", "PUT", "DELETE" },
["/_aliases"] = { "GET", "POST" }
}
}
-- get authenticated user as role
local role = ngx.var.remote_user
ngx.log(ngx.DEBUG, role)
-- exit 403 when no matching role has been found
if restrictions[role] == nil then
ngx.header.content_type = 'text/plain'
ngx.log(ngx.WARN, "Unknown role ["..role.."]")
ngx.status = 403
ngx.say("403 Forbidden: You don\'t have access to this resource.")
return ngx.exit(403)
end
-- get URL
local uri = ngx.var.uri
ngx.log(ngx.DEBUG, uri)
-- get method
local method = ngx.req.get_method()
ngx.log(ngx.DEBUG, method)
local allowed = false
for path, methods in pairs(restrictions[role]) do
-- path matched rules?
local p = string.match(uri, path)
local m = nil
-- method matched rules?
for _, _method in pairs(methods) do
m = m and m or string.match(method, _method)
end
if p and m then
allowed = true
ngx.log(ngx.NOTICE, method.." "..uri.." matched: "..tostring(m).." "..tostring(path).." for "..role)
break
end
end
if not allowed then
ngx.header.content_type = 'text/plain'
ngx.log(ngx.WARN, "Role ["..role.."] not allowed to access the resource ["..method.." "..uri.."]")
ngx.status = 403
ngx.say("403 Forbidden: You don\'t have access to this resource.")
return ngx.exit(403)
end
# Generate passwords:
#
# $ printf "nobody:$(openssl passwd -crypt nobody)\n" >> passwords
# $ printf "all:$(openssl passwd -crypt all)\n" >> passwords
# $ printf "user:$(openssl passwd -crypt user)\n" >> passwords
# $ printf "admin:$(openssl passwd -crypt admin)\n" >> passwords
#
# Install the Nginx with Lua support ("openresty"):
#
# $ wget http://openresty.org/download/ngx_openresty-1.4.3.9.tar.gz
# $ tar xf ngx_openresty-*
# $ cd ngx_openresty-*
# $
# $ ./configure --with-luajit
# $ # ./configure --with-luajit --with-cc-opt="-I/usr/local/include" --with-ld-opt="-L/usr/local/lib" # Mac OS X w/ Homebrew
# $ make && make install
#
# More information: http://openresty.org/#Installation
#
# See the Lua source code in `authorize.lua`
#
# Run:
#
# $ /usr/local/openresty/nginx/sbin/nginx -p $PWD/nginx/ -c $PWD/nginx_authorize_by_lua.conf
worker_processes 1;
error_log logs/lua.log notice;
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
keepalive 15;
}
server {
listen 8080;
location / {
auth_basic "Protected Elasticsearch";
auth_basic_user_file passwords;
access_by_lua_file '../authorize.lua';
proxy_pass http://elasticsearch;
proxy_redirect off;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "Keep-Alive";
proxy_set_header Proxy-Connection "Keep-Alive";
}
}
}
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_basic_proxy.conf
# $ curl localhost:8000
#
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
proxy_pass http://localhost:9200;
}
}
}
# Generate password with eg.
#
# $ printf "john:$(openssl passwd -crypt s3cr3t)\n" > passwords
#
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_allow_path.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
}
server {
listen 8080;
auth_basic "Protected Elasticsearch";
auth_basic_user_file passwords;
location ~* ^(/_cluster|/_nodes) {
proxy_pass http://elasticsearch;
proxy_redirect off;
}
location / {
return 403;
break;
}
}
}
# Generate password with eg.
#
# $ printf "john:$(openssl passwd -crypt s3cr3t)\n" > passwords
#
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_allow_path_and_method.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
}
server {
listen 8080;
location / {
error_page 590 = @elasticsearch;
error_page 595 = @protected_elasticsearch;
set $ok 0;
if ($request_uri ~ ^/$) {
set $ok "${ok}1";
}
if ($request_method = HEAD) {
set $ok "${ok}2";
}
if ($ok = 012) {
return 590;
}
return 595;
}
location @elasticsearch {
proxy_pass http://elasticsearch;
proxy_redirect off;
}
location @protected_elasticsearch {
auth_basic "Protected Elasticsearch";
auth_basic_user_file passwords;
proxy_pass http://elasticsearch;
proxy_redirect off;
}
}
}
# Generate password with eg.
#
# $ printf "john:$(openssl passwd -crypt s3cr3t)\n" > passwords
#
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_basic.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
}
server {
listen 8080;
auth_basic "Protected Elasticsearch";
auth_basic_user_file passwords;
location / {
proxy_pass http://elasticsearch;
proxy_redirect off;
}
}
}
# Generate password with eg.
#
# $ printf "john:$(openssl passwd -crypt s3cr3t)\n" > passwords
#
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_deny_path.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
}
server {
listen 8080;
auth_basic "Protected Elasticsearch";
auth_basic_user_file passwords;
location / {
if ($request_filename ~ _shutdown) {
return 403;
break;
}
proxy_pass http://elasticsearch;
proxy_redirect off;
}
}
}
# Generate passwords:
#
# $ printf "user:$(openssl passwd -crypt user)\n" > users
# $ printf "admin:$(openssl passwd -crypt admin)\n" > admins
#
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_roles.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
}
# Allow HEAD / for all
#
# curl -i -X HEAD 'http://localhost:8080'
# HTTP/1.1 200 OK
#
# curl -i -X GET 'http://localhost:8080'
# HTTP/1.1 403 Forbidden
#
server {
listen 8080;
server_name elasticsearch_all.local;
location / {
return 401;
}
location = / {
if ($request_method !~ "HEAD") {
return 403;
break;
}
proxy_pass http://elasticsearch;
proxy_redirect off;
}
}
# Allow access to /_search and /_analyze for authenticated "users"
#
# curl -i 'http://localhost:8081/_search'
# HTTP/1.1 401 Unauthorized
#
# curl -i 'http://user:user@localhost:8081/_search'
# HTTP/1.1 200 OK
#
# curl -i 'http://user:user@localhost:8081/_analyze?text=Test'
# HTTP/1.1 200 OK
#
# curl -i 'http://user:user@localhost:8081/_cluster/health'
# HTTP/1.1 403 Forbidden
#
server {
listen 8081;
server_name elasticsearch_users.local;
auth_basic "Elasticsearch Users";
auth_basic_user_file users;
location / {
return 403;
}
location ~* ^(/_search|/_analyze) {
proxy_pass http://elasticsearch;
proxy_redirect off;
}
}
# Allow access to anything for authenticated "admins"
#
# curl -i 'http://admin:admin@localhost:8082/_search'
# HTTP/1.1 200 OK
#
# curl -i 'http://admin:admin@localhost:8082/_cluster/health'
# HTTP/1.1 200 OK
#
server {
listen 8082;
server_name elasticsearch_admins.local;
auth_basic "Elasticsearch Admins";
auth_basic_user_file admins;
location / {
proxy_pass http://elasticsearch;
proxy_redirect off;
}
}
}
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_keep_alive.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
keepalive 15;
}
server {
listen 8080;
location / {
proxy_pass http://elasticsearch;
proxy_http_version 1.1;
proxy_set_header Connection "Keep-Alive";
proxy_set_header Proxy-Connection "Keep-Alive";
}
}
}
# Run:
#
# $ nginx -p $PWD/nginx/ -c $PWD/nginx_load_balancer.conf
#
events {
worker_connections 1024;
}
http {
upstream elasticsearch {
server 127.0.0.1:9200;
server 127.0.0.1:9201;
server 127.0.0.1:9202;
}
server {
listen 8080;
server_name elasticsearch_proxy;
location / {
proxy_pass http://elasticsearch;
}
}
}
john:C8Qf0PmsRcK7s
all:gR9A3zOlFFRDs
user:d.q1Wcp0KUqsk
admin:/XyxrPc65koRY
user:laYuBcroEif0c
@cleesmith
Copy link

Great article! ... with the code too ... thanks

@jreinke
Copy link

jreinke commented Feb 20, 2015

Great work thank you.

@ysangkok
Copy link

is it really necessary to specify keep alive for proxy connections to get it? I do not understand why it is not the default...

@moy2010
Copy link

moy2010 commented Jun 26, 2017

In Nginx's documentation, it states that the Connection header should be cleared for keep-alive to work:

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive

Specifying the keep-alive connection header was meant to be used with HTTP 1.0, thus it's no longer recommended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment