├── Dockerfile ├── LICENSE ├── README.md ├── eth-jsonrpc-access.lua └── nginx.conf /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | 3 | MAINTAINER Antoine Detante "antoine.detante@gmail.com" 4 | 5 | RUN apt-get update && apt-get install -y build-essential curl libssl-dev libluajit-5.1-dev libpcre3-dev zlib1g-dev 6 | 7 | RUN mkdir /var/log/nginx 8 | 9 | RUN mkdir /opt/src \ 10 | && curl -L http://nginx.org/download/nginx-1.11.7.tar.gz 2> /dev/null > /opt/src/nginx-1.11.7.tar.gz \ 11 | && curl -L https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz 2> /dev/null > /opt/src/ngx_devel_kit-0.3.0.tar.gz \ 12 | && curl -L https://github.com/openresty/lua-nginx-module/archive/v0.10.7.tar.gz 2> /dev/null > /opt/src/lua-nginx-module-0.10.7.tar.gz \ 13 | && curl -L https://github.com/mpx/lua-cjson/archive/2.1.0.tar.gz 2> /dev/null > /opt/src/lua-cjson-2.1.0.tar.gz 14 | 15 | RUN cd /opt/src && tar xfz lua-cjson-2.1.0.tar.gz && cd lua-cjson-2.1.0 \ 16 | && sed -i.bak 's/LUA_INCLUDE_DIR =.*/LUA_INCLUDE_DIR = \/usr\/include\/luajit-2.0/g' Makefile \ 17 | && sed -i.bak 's/LUA_MODULE_DIR =.*/LUA_MODULE_DIR = \/usr\/share\/luajit-2.0.3\/jitg/g' Makefile \ 18 | && make && make install 19 | 20 | RUN cd /opt/src && tar xfz nginx-1.11.7.tar.gz && tar xfz ngx_devel_kit-0.3.0.tar.gz && tar xfz lua-nginx-module-0.10.7.tar.gz 21 | 22 | RUN cd /opt/src/nginx-1.11.7 && ./configure --prefix=/opt/nginx --with-ld-opt="-Wl,-rpath,/usr/local/lib" --add-module=/opt/src/ngx_devel_kit-0.3.0 --add-module=/opt/src/lua-nginx-module-0.10.7 --with-http_ssl_module \ 23 | && make && make install \ 24 | && rm /opt/src/*.tar.gz 25 | 26 | ADD nginx.conf /etc/nginx.conf 27 | ADD eth-jsonrpc-access.lua /opt/nginx/eth-jsonrpc-access.lua 28 | 29 | EXPOSE 80 443 30 | 31 | CMD ["/opt/nginx/sbin/nginx", "-c", "/etc/nginx.conf"] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Antoine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ethereum Nginx Proxy 2 | ==================== 3 | 4 | This repo allows exposing a Ethereum JSON-RPC server to remote hosts using nginx as a reverse proxy. 5 | For security purpose, an Ethereum client (geth, parity, ...) exposes the JSON-RPC service only to localhost interface. In some cases (permisionned chain by example), it can be interesting to expose JSON-RPC to remote hosts but with limited access. 6 | 7 | This solution is to use a LUA script, used with nginx-lua-module to control access to the upstream Ethereum client. You can blacklist or whitelist certain JSON-RPC methods (for example, authorize only JSON-RPC requests to `eth_sendRawTransaction` to allow remote hosts to send transactions to the client, without allowing them to read states in the chain). 8 | 9 | Why nginx? 10 | ---------- 11 | 12 | nginx has some interesting characteristics as a reverse proxy on top of Ethereum JSON-RPC service: 13 | 14 | * Can expose the JSON-RPC service through a TLS channel 15 | * Can load balance between multiple JSON-RPC backends 16 | * Can forward HTTP incoming traffic to the IPC interface of Ethereum client 17 | * Can plug standard authentication on top of the JSON-RPC interface (basic authentication, TLS mutual authentication, ...) 18 | 19 | 20 | Installation 21 | ------------ 22 | 23 | The script `eth-jsonrpc-access.lua` must be put in your nginx, to be used from `access_by_lua_file` directive. 24 | In order to have a fully functional nginx proxy, you have to install: 25 | 26 | * lua-nginx-module, see the setup [here](https://github.com/openresty/lua-nginx-module#installation). 27 | * cjson lua package installed, see [here](https://www.kyne.com.au/~mark/software/lua-cjson.php) 28 | 29 | Or you can use an adapt the provided Docker image (see [Docker image section](#docker-image)). 30 | 31 | 32 | Usage 33 | ----- 34 | 35 | The lua script must be used in a `location` nginx using the `access_by_lua_file` directive. 36 | 37 | You have to set one of `jsonrpc_whitelist` or `jsonrpc_blacklist` nginx variable to restrict the list of JSON-RPC methods available for this location, separated by commas: 38 | 39 | 40 | ``` 41 | location / { 42 | set $jsonrpc_whitelist 'eth_getTransactionByHash,eth_getTransactionReceipt,eth_sendRawTransaction'; 43 | access_by_lua_file 'eth-jsonrpc-access.lua'; 44 | proxy_pass http://localhost:8545; 45 | } 46 | ``` 47 | 48 | With the configuration above, only `eth_getTransactionByHash`, `eth_getTransactionReceipt`, `eth_sendRawTransaction` calls will be fowarded to JSON-RPC interface at `http://localhost:8545`, other requests will be rejected with HTTP 403 status. 49 | 50 | You can find a full `nginx.conf` example file in the repo. 51 | 52 | Docker image 53 | ------------ 54 | 55 | A ready to use image is published to Docker Hub. 56 | 57 | Pull the image: 58 | `docker pull antoine/ethereum-nginx-proxy:latest` 59 | 60 | And run a container with your custom `nginx.conf`: 61 | `docker run -p 8080:8080 -v /my/nginx.conf:/etc/nginx.conf antoine/ethereum-nginx-proxy:latest` 62 | 63 | This image is based on Nginx 1.11.7, Lua Nginx module 0.10.7 and Lua CJSON 2.1.0. -------------------------------------------------------------------------------- /eth-jsonrpc-access.lua: -------------------------------------------------------------------------------- 1 | local cjson = require('cjson') 2 | 3 | local function empty(s) 4 | return s == nil or s == '' 5 | end 6 | 7 | local function split(s) 8 | local res = {} 9 | local i = 1 10 | for v in string.gmatch(s, "([^,]+)") do 11 | res[i] = v 12 | i = i + 1 13 | end 14 | return res 15 | end 16 | 17 | local function contains(arr, val) 18 | for i, v in ipairs (arr) do 19 | if v == val then 20 | return true 21 | end 22 | end 23 | return false 24 | end 25 | 26 | -- parse conf 27 | local blacklist, whitelist = nil 28 | if not empty(ngx.var.jsonrpc_blacklist) then 29 | blacklist = split(ngx.var.jsonrpc_blacklist) 30 | end 31 | if not empty(ngx.var.jsonrpc_whitelist) then 32 | whitelist = split(ngx.var.jsonrpc_whitelist) 33 | end 34 | 35 | -- check conf 36 | if blacklist ~= nil and whitelist ~= nil then 37 | ngx.log(ngx.ERR, 'invalid conf: jsonrpc_blacklist and jsonrpc_whitelist are both set') 38 | ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) 39 | return 40 | end 41 | 42 | -- get request content 43 | ngx.req.read_body() 44 | 45 | -- try to parse the body as JSON 46 | local success, body = pcall(cjson.decode, ngx.var.request_body); 47 | if not success then 48 | ngx.log(ngx.ERR, 'invalid JSON request') 49 | ngx.exit(ngx.HTTP_BAD_REQUEST) 50 | return 51 | end 52 | 53 | local method = body['method'] 54 | local version = body['jsonrpc'] 55 | 56 | -- check we have a method and a version 57 | if empty(method) or empty(version) then 58 | ngx.log(ngx.ERR, 'no method and/or jsonrpc attribute') 59 | ngx.exit(ngx.HTTP_BAD_REQUEST) 60 | return 61 | end 62 | 63 | -- check the version is supported 64 | if version ~= "2.0" then 65 | ngx.log(ngx.ERR, 'jsonrpc version not supported: ' .. version) 66 | ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) 67 | return 68 | end 69 | 70 | -- if whitelist is configured, check that the method is whitelisted 71 | if whitelist ~= nil then 72 | if not contains(whitelist, method) then 73 | ngx.log(ngx.ERR, 'jsonrpc method is not whitelisted: ' .. method) 74 | ngx.exit(ngx.HTTP_FORBIDDEN) 75 | return 76 | end 77 | end 78 | 79 | -- if blacklist is configured, check that the method is not blacklisted 80 | if blacklist ~= nil then 81 | if contains(blacklist, method) then 82 | ngx.log(ngx.ERR, 'jsonrpc method is blacklisted: ' .. method) 83 | ngx.exit(ngx.HTTP_FORBIDDEN) 84 | return 85 | end 86 | end 87 | 88 | return 89 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 5; 2 | daemon off; 3 | error_log /var/log/nginx/error.log; 4 | pid /var/log/nginx/nginx.pid; 5 | worker_rlimit_nofile 8192; 6 | 7 | events { 8 | worker_connections 4096; 9 | } 10 | 11 | http { 12 | include /opt/nginx/conf/mime.types; 13 | index index.html index.htm index.php; 14 | 15 | upstream ethereum { 16 | server ETHEREUM_CLIENT_IP:8545; 17 | } 18 | 19 | default_type application/octet-stream; 20 | log_format main '$remote_addr - $remote_user [$time_local] $status ' 21 | '"$request" $body_bytes_sent "$http_referer" ' 22 | '"$http_user_agent" "$http_x_forwarded_for"'; 23 | access_log /var/log/nginx/access.log main; 24 | sendfile on; 25 | tcp_nopush on; 26 | 27 | server { 28 | listen 8080; 29 | server_name localhost; 30 | 31 | location / { 32 | set $jsonrpc_whitelist 'eth_getTransactionByHash,eth_getTransactionReceipt,eth_sendRawTransaction'; 33 | access_by_lua_file 'eth-jsonrpc-access.lua'; 34 | proxy_pass http://ethereum; 35 | } 36 | } 37 | 38 | } 39 | --------------------------------------------------------------------------------