×
广告

使用nginx的http验证功能对elasticsearch加密

96
zhnliving
2016.08.20 10:43* 字数 1417

时间:2016-08-20

前言

这篇文章是https://www.elastic.co/blog/playing-http-tricks-nginx的部分翻译,对像我这样的小白学习nginx和elasticsearch有些许帮助,ps:翻译好难。。。e文好的同学还是去看原文吧。。。

正文

Elasticsearch默认是完全暴露在互联网上的RESTful服务接口。
这样做的好处是:web开发人员可以很快熟悉它的API,很容易使用curl命令或者浏览器来查询数据。也非常容易使用各种编程语言封装对应的接口。
Elasticsearch的RESTful接口是基于http开发的,所以它与现有的网络架构兼容性很好。

使用RESTful作为软件架构

现在很多软件使用RESTful服务作为基础架构,这样做的一大好处是你可以很容易插入新功能和修改已有功能。

Nginx

Nginx是一个高性能的反向代理服务。许多大型PHP项目也会使用nginx为静态文件加速。这篇文章使用基础的nginx配置为elasticsearch加上验证和授权功能。
运行Nginx为Elasticsearch做一个端口映射,我们可以非常简单的这样配置:

http {
  server {
    listen 8080;
    location / {
      proxy_pass http://localhost:9200;
    }
  }
}

当访问http://localhost:8080的时候,相当于访问http://localhost:9200
这种配置当然没什么大用,聪明的读者会发现了nginx其实已经做了些工作,比如记录了每次请求的日志。

连接持久化

使用nginx保持elasticsearch连接。这么做可以减少elasticsearch因每次请求都需要连接~断开的压力,节省资源。
更多配置请参考https://gist.github.com/karmi/b0a9b4c111ed3023a52d

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";
    }

  }

}

启动nginx

$ nginx -p $PWD/nginx/ -c $PWD/nginx_keep_alive.conf

没有持久化连接的时候,每次请求elasticsearch数据opened connections都会增加:

$ curl 'localhost:9200/_nodes/stats/http?pretty' | grep total_opened
# "total_opened" : 13
$ curl 'localhost:9200/_nodes/stats/http?pretty' | grep total_opened
# "total_opened" : 14
# ...

使用nginx持久化连接后,opened connections每回都一样:

$ curl 'localhost:8080/_nodes/stats/http?pretty' | grep total_opened
# "total_opened" : 15
$ curl 'localhost:9200/_nodes/stats/http?pretty' | grep total_opened
# "total_opened" : 15
# ...

简单的负载均衡

对这个配置做一个非常小的改动,我们就可以分配requests到不同的elasticsearch节点上:

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;

    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";
    }

  }

}

这里我们添加了两个节点在upstream中。Nginx会自动分配(轮询)request到不同的节点中。

$ curl localhost:8080 | grep name
# "name" : "Silver Fox",
$ curl localhost:8080 | grep name
# "name" : "G-Force",
$ curl localhost:8080 | grep name
# "name" : "Helleyes",
$ curl localhost:8080 | grep name
# "name" : "Silver Fox",
# ...

这么做可以将请求分散给所有节点,增加节点只需在upstream中增加url,然后重启nginx
关于更高级的负载均衡功能,请参考Load Balancing with NGINX and NGINX Plus
(注意,elasticsearch本身有负载均衡机制。)

基本的用户认证

默认情况下,elasticsearch不采取认证机制,因为它不是设计在开放的网络环境当中,当你打开9200端口,你的数据,集群都不是安全的。
通常保护elasticsearch集群是通过VPN,防火墙等手段限制。但是如果你希望在外网连接elasticsearch集群,就只能通过用户密码来加以认证了。
可以使用nginx来实现用户认证:

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;
    }
  }

}

可以使用很多工具来生成密码,这里使用openssl:

$ printf "john:$(openssl passwd -crypt s3cr3t)\n" > passwords

运行nginx:

$ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_basic.conf

普通请求会被拒绝:

$ curl -i localhost:8080
# HTTP/1.1 401 Unauthorized
# ...

输入帐号密码后,就可以建立连接了:

$ curl -i john:s3cr3t@localhost:8080
# HTTP/1.1 200 OK
# ...

现在可以关闭9200端口,只留8080端口对外开放了,想要连接集群必须得通过验证。

简单授权

如果没有授权机制,只要登录,就可以对集群中任何事情,更改或删除数据,查看内部数据,甚至关闭集群。
通过非常小的改动,我们就可以拒绝通过RESTful命令关闭集群。

location / {
  if ($request_filename ~ _shutdown) {
    return 403;
    break;
  }

  proxy_pass http://elasticsearch;
  proxy_redirect off;
}

重启nginx:

$ nginx -p $PWD/nginx/ -c $PWD/nginx_http_auth_deny_path.conf

尝试调用关闭集群,将会返回拒绝信息:

$ curl -i -X POST john:s3cr3t@localhost:8080/_cluster/nodes/_shutdown
# HTTP/1.1 403 Forbidden
# ....

也可以配置允许某些请求,拒绝其余的请求,通过两个location来实现:

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;
    }
  }

}

这样,请求/_cluster 和 /_nodes将通过,其他的会被拒绝:

$ curl -i john:s3cr3t@localhost:8080/
HTTP/1.1 403 Forbidden
# ...

$ curl -i john:s3cr3t@localhost:8080/_cluster/health
# HTTP/1.1 200 OK
# ...

$ curl -i john:s3cr3t@localhost:8080/_nodes/stats
HTTP/1.1 200 OK
# ...

选择性认证

现在我们想用基本认证保护elasticsearch但是允许ping命令发送HEAD请求到/起到监视集群状态的作用。

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;
    }
  }

}

首先,我们定义两个状态代码:590-不需要验证,595-需要验证,使用nginx的location功能,两个location都指向同一个集群,但是其中一个需要认证。
然后我们设置一个变量$ok,默认值0,当进入请求是/,$ok变成01.如果请求是HEAD$ok变成012。
如果$ok是012,返回590状态码,换句话说,不需验证,其他情况返回595,需要验证。

$ curl -i -X HEAD localhost:8080
# HTTP/1.1 200 OK
# ...

$ curl -i localhost:8080
# HTTP/1.1 401 Unauthorized
# ...

$ curl -i john:s3cr3t@localhost:8080
# HTTP/1.1 200 OK
# ...

多角色授权

到这里,我们已经有了一个简单的认证机制。如果我们需要一个更宽泛的认证机制,基于角色的,不如说像下面这样的:

  • 没有认证的用户只能使用ping命令,ping指定的URL(/);
  • 认证过的user用户可以执行_search与_analyze请求;
  • 认证过的admin用户可以执行任何请求。
    我们使用不同的端口为不同的角色服务:
events {
  worker_connections  1024;
}

http {

  upstream elasticsearch {
      server 127.0.0.1:9200;
  }

  # Allow HEAD / for all
  #
  server {
      listen 8080;

      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"
  #
  server {
      listen 8081;

      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"
  #
  server {
      listen 8082;

      auth_basic           "Elasticsearch Admins";
      auth_basic_user_file admins;

      location / {
        proxy_pass http://elasticsearch;
        proxy_redirect off;
      }
  }

}

点用openssl命令生成认证文件:

$ printf "user:$(openssl passwd -crypt user)\n"   > users
$ printf "admin:$(openssl passwd -crypt admin)\n" > admins

现在,所有用户都能ping集群,但是做不了其他操作:

$ curl -i -X HEAD localhost:8080
# HTTP/1.1 200 OK
$ curl -i -X GET localhost:8080
# HTTP/1.1 403 Forbidden

user用户可以执行search,analyze请求,其他的不可以:

$ curl -i localhost:8081/_search
# HTTP/1.1 401 Unauthorized
# ...

$ curl -i user:user@localhost:8081/_search
# HTTP/1.1 200 OK
# ...

$ curl -i user:user@localhost:8081/_analyze?text=Test
# HTTP/1.1 200 OK
# ...

$ curl -i user:user@localhost:8081/_cluster/health
# HTTP/1.1 403 Forbidden
# ...

admin用户可以做任何操作:

$ curl -i admin:admin@localhost:8082/_search
# HTTP/1.1 200 OK
# ...

$ curl -i admin:admin@localhost:8082/_cluster/health
# HTTP/1.1 200 OK
# ...

这样轻松解决了基于多角色的授权,这么做的代价是:每一种角色都使用不同的端口。

日记本
Web note ad 1