geth的rpc采用加密访问方式

1 为什么RPC要加密访问

geth给用户暴露了很多Json-RPC接口API,可以让web3.js或web3j库来通过这些接口来访问geth节点,可以实现远程查询转账等功能。但是geth暴露的这些接口是未经过任何加密处理的不安全接口。如果geth使用这种方式启动RPC:

$ geth — rpc — rpcaddr 0.0.0.0 — rpcport 8545 — rpcapi “db, eth, net, web3, personal” — ipcapi “admin,eth,debug,personal,web3” 

--rpcaddr 0.0.0.0 这个允许所有用户都访问rpc端口。这样外部用户都可以访问这个geth节点,geth暴露在公网上面,可能会遭受黑客各种攻击。比如DDoS攻击。博主前几天的geth节点遭受过一种攻击,geth开着几分钟,CPU使用率和内存很快飙到100%,紧接着就被系统杀掉进程了。攻击情况见下图。

慢雾团队披露的黑客从用户存在geth节点上的钱包偷币的事件,就是黑客通过暴露在外网的RPC接口作案的例子。按照最新的研究报告,凡是在暴露在公网的geth节点上解锁过的钱包,都可能存在安全风险,即使已经关闭RPC端口,黑客仍然可以通过已经用钱包签名过的数据随时从用户钱包中把数字货币偷走。

如果geth节点不得不暴露在公网上面,则会面临很多安全风险。一个可能的补救措施是对RPC访问进行加密。通过nginx的HTTP basic Auth(Http基本认证)技术,可以实现更高的安全。

2 Nginx Basic HTTP Authentication原理

通过配置nginx的反向代理和加密技术,可以给运行在linux上的应用程序分配一个新的url,访问应用程序就相当于访问这个url。外部用户想访问这个url,必须输入用户名和密码,否则访问会被拒绝。对于geth节点,以前必须对所有用户暴露rpc端口,采用NBHA技术,则给geth分配一个对于的url,需要用户名和密码才能访问geth节点。这个时候,geth节点不必对外开放RPC端口。

3 Nginx Basic HTTP Authentication实现

下面以ubuntu16.04操作系统为例,讲解在利用nginx实现Http基本认证的步骤。

步骤1 安装nginx软件。

sudo apt-get isntall nginx apache2-utils

步骤2 创建认证用户名和密码

$ sudo htpasswd -c /etc/nginx/geth.htpasswd nginx

将在/etc/nginx下创建名为geth.htpasswd的密码文件,用户名设置为nginx。输入上面命令,会提示用户输入俩次密码。

步骤3 修改nginx配置

打开文件 /etc/nginx/sites-enabled/default文件,将里面的内容修改成这样:

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html;

	server_name localhost;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}

	location /eth {
    	#try_files $uri $uri/ =404;
    	auth_basic "Restricted Area";
    	auth_basic_user_file /etc/nginx/geth.htpasswd;
		proxy_pass http://localhost:8100;
  	}
	
}

在这里将服务器的名字设为localhost,geth对应的url为localhost/eth,其对应的密码文件通过auth_basic_user_file指令进行制定。

步骤4 更新nginx服务

$ sudo service nginx reload

步骤5 启动geth节点

geth --rpc --rpcaddr 0.0.0.0 --rpcapi db,eth,net,web3,personal --rpcport 8100 --datadir=./ --networkid 11 console

此时可以用浏览器中访问127.0.0.1/eth看什么效果:

当输入正确的用户名和密码后点击OK,如果没有出现错误,这说明配置成功。

到现在为止,已经成功的用nginx为geth构建了一层安全防护,并给geth映射了一个外部访问的url,现在访问geth不必通过http://<ip>:<port>的方式,而是直接访问映射的url。所以现在是时候把geth暴露出去的rpc端口采取禁止外部访问的操作了。腾讯云阿里云都有安全组可以实现这个操作。

3 采用web3j访问

上面给geth映射的外部url为http://192.168.1.90/eth,其中192.168.1.90是我的局域网ip。web3j使用的Http模块为OkHttp3,认证需要的用户名和密码信息可以按照OkHttp3添加认证的方式来添加。没使用Http认证时,web3j构建Admin对象的方式是:

Admin ethClient;
ethClient = Admin.build(new HttpService(url));

加入认证用户名和密码的方式:

private static OkHttpClient buildBasicAuthClient() {
        return new OkHttpClient.Builder().authenticator(new Authenticator() {
            @Override
            public Request authenticate(Route route, Response response) throws IOException {
                String credential = okhttp3.Credentials.basic(Define.userName, Define.passwd);
                return response.request().newBuilder().header("Authorization", credential).build();
            }
        }).build();
 }

Admin ethClient;
ethClient = Admin.build(new HttpService(url,buildBasicAuthClient(),false));

OK,现在可以用Web3j访问加入了Http认证保护的geth节点了。

4 ios端调用web3.js来访问Http Basic Authentication保护资源的问题

ios客户端访问geth的方式跟安卓端不一样。由于没有开源成熟的OC语言的类似于web3j的库,ios端只有通过webview的方式建立一个Html页面,在页面里通过js来调用web3.js的API函数来访问geth。在浏览器里面访问有Http Basic Authentication的geth节点,会报错:

XMLHttpRequest cannot load http://192.168.1.90/eth. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 401.

这是浏览器在报js跨域访问的问题。一番谷歌后仿照网上解决CROS的方法,通过添加配置nginx的配置文件添加:

if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
     }

这个时候还是报错:

The 'Access-Control-Allow-Origin' header contains multiple values 'null, *', but only one is allowed. Origin 'null' is therefore not allowed access.

浏览器里面抓包显示:

这个时候很奇怪有俩个Access-Control-Allow-Origin,为null的那次引起了这个错误。这是由于额外添加了几个Header引起的。解决方法是配置文件只保留Options那个Header。最终的配置:

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html;

	server_name localhost;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}

	location /eth {
    	#try_files $uri $uri/ =404;
    	auth_basic "Restricted Area";
    	auth_basic_user_file /etc/nginx/geth.htpasswd;
		proxy_pass http://localhost:8100;
		if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
     }
  	}
	
}

猜你喜欢

转载自blog.csdn.net/liuzhijun301/article/details/81085765