2022年5月

前言

在很久很久以前,一位萌新程序员小明用Docker安装了Elasticsearch,之后很久很久没有在管过了,但偶然的一次对服务器检查发现ES中存在一堆的数据,小明立马意识到ES的服务被他人使用了,急忙关闭了服务并查询如何对ES进行添加鉴权。

技术点

Elasticsearch,Nginx(可选)

实战

第一种:ES启用鉴权

本次ES运行版本为7.17.2,不同的版本可能会存在配置不同,可能需要查询下当前版本的手册较佳。

elasticsearch.yml

对配置文件添加如下配置

http.cors.enabled: true
http.cors.allow-origin: "*"
http.cors.allow-headers: Authorization
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true

添加配置后重启服务发现再次访问ES服务则需要提示输入账户密码,点击取消会有401的错误提示.

配置密码

配置完上一步,此时服务是不可访问,,因此我们还需要在终端配置密码。

术哥@ubuntu:/usr/share/elasticsearch# elasticsearch-setup-passwords interactive
Initiating the setup of passwords for reserved users elastic,apm_system,kibana,kibana_system,logstash_system,beats_system,remote_monitoring_user.
You will be prompted to enter passwords as the process progresses.
Please confirm that you would like to continue [y/N] y

Enter password for [elastic]: 
Reenter password for [elastic]: 
Enter password for [apm_system]: 
Reenter password for [apm_system]: 
Enter password for [kibana_system]: 
Reenter password for [kibana_system]:  
Enter password for [logstash_system]: 
Reenter password for [logstash_system]: 
Enter password for [beats_system]: 
Reenter password for [beats_system]: 
Enter password for [remote_monitoring_user]: 
Reenter password for [remote_monitoring_user]: 
Changed password for user [apm_system]
Changed password for user [kibana_system]
Changed password for user [kibana]
Changed password for user [logstash_system]
Changed password for user [beats_system]
Changed password for user [remote_monitoring_user]
Changed password for user [elastic]

Change完成后就重启服务,访问ES服务,输入账号elastic和你设置的密码既可。

第二种:使用Nginx添加Basic鉴权

此方法就比较简单了,不需要得ES内进行配置,则使用Nginx进行端口转发且添加Basic鉴权,就直接上配置文件了。

如何生成鉴权文件可参考上一篇文章【小技巧】为Web再加上一把锁

配置文件

注意:此配置文件只显示需要改动点,并不能直接复制粘贴使用


steam es{
    server 127.0.0.1:9200
}

server {

    auth_basic "admin area"; # 启用鉴权
    auth_basic_user_file "/etc/nginx/http_auth/.{domain}.pass"; # 指定鉴权文件

    location / {
        http_proxy  es;
    }
}

成果

鉴权成功

[图片未上传]

鉴权失败

[图片未上传]

额外的点

Kibana鉴权

由于我们为ES添加了鉴权,因此Kibana这块也需要在配置文件上添加对应配置

[配置中心未找到KEY]

可能会遇到的问题?

ERROR: X-Pack Security is disabled by configuration.

术哥@ubuntu:/usr/share/elasticsearch# elasticsearch-setup-passwords interactive

Unexpected response code [500] from calling GET http://127.0.0.1:9200/_security/_authenticate?pretty
It doesn't look like the X-Pack security feature is enabled on this Elasticsearch node.
Please check if you have enabled X-Pack security in your elasticsearch.yml configuration file.


ERROR: X-Pack Security is disabled by configuration.

你服务压根没重启吧。

前言

有几次在学习的时候,官方提供的工具刚好是一个对Web工具没有任何加密措施,毕竟学习是无时无刻(说白了就是无时无刻的卷),虽然都是一些学习数据,将服务直接暴露在公网上总是觉得不安全,因此对Web服务额外进行Basic验证。

技术点

Nginx+htpasswd

实战

如果你的机器上安装的是Apache服务,那么htpasswd也许是会附带安装的,但如果你安装的是Nginx服务,那也不用担心,对此我们也可以额外安装这个工具。

htpasswd

安装htpasswd

sudo apt install apache2-utils

生成鉴权文件

htpasswd -bc .{domain}.pass {username} {password}

建议对生成的鉴权文件加上.进行基础隐藏操作,防止被扫描到

htpasswd命令详解

htpasswd命令详解

Nginx

配置文件

生成鉴权的文件后,我们要对需要加上的server域名下添加以下两条

 auth_basic "admin area"; # 启用鉴权
 auth_basic_user_file "/etc/nginx/http_auth/.{domain}.pass"; # 指定鉴权文件

添加完成后重启服务即可。

成果

成果

小提示

  • 建议对HTTP服务加上SSL证书,以防中间人窃听密码。

Nested是什么?

  • 直观的说,Nested实际上就是Object的

    数组

    。如下,这个user就是个nested结构

    {
    "user" : [ 
      {
        "first" : "John",
        "last" :  "Smith"
      },
      {
        "first" : "Alice",
        "last" :  "White"
      }
    ]
    }

Nested 和 Object 是什么关系?

  • ES原生支持Object类型,也就是任意字段都可以是个对象,而ES又是所有字段都是多值,也就是都可以是list。那么在ES中Nested和Object List又是什么关系呢?
  • 这就要从Object说起了。Object虽然是个对象,但是实际存储时是在当前文档里打平存储的。如上那个例子,如果只有一个user,那么在真实索引中实际上是下面这样的
{
  "user.first" : "John",
  "user.last" : "Smith"
}
  • 而如果是个list,那么就成了
{
  "user.first" : ["John","Alice"],
  "user.last" : ["Smith","White"]
}
  • 因为建索引时打平,因此检索时ES就无法知道到底是John Smith还是John White了。因此引入了Nested结构。
  • Nested将list里的每个doc单独变成子文档进行存储,因此在查询时就可以知道具体的结构信息了。

Nested 查询要注意什么?

  • Nested因为是单独的子文档存储,因此在使用时,直接用 a.b.c 是无法访问的,需要将其套在nested查询里。除此之外,和其他的查询并无差异。
{
  "query": {
    "nested": {
      "path": "user",
      "query": {
        "match": {"user.first" : "John"}
      },
      "inner_hits": {}
    }
  }
}
  • 如上所示,用一个nested套住真实query即可。默认的hit是返回父文档,也就是大的doc。如果加上inner_hits会在父文档的source中多一个inner_hits的字段,返回真实命中的object,其中有个offset表明list数组下标。
  • 需要注意的是,由于单独存储很耗资源,因此默认一个index最多只有50个nested字段。此外,虽然nested是单独存储的,但是其字段数也算入index总字段数,默认最多1000个。

Nested Aggregation是什么?

  • 对于Nested结构,有一点需要谨记的,就是他是个List结构。Nested Agg就是对这个list做agg操作,agg写法和普通的一样,只需要在外面套上nested即可。
  • 如官方文档的例子,就是一个商品有许多卖家,对这些卖家的报价求最小值。

能否用Nested做动态kv?

  • Nested除了存储固定的Object List,还有一种常用的场景就是用来存储动态的KV。虽然ES天然支持dynamic mapping,但是其key都是固化在每一个doc中的,如果存储用户自定义报表数据。每个用户的key差异很大,放在同一张表会出现大量空值。这是很浪费系统资源的行为,并且随着Key的不断增多,最终会超出index的最大key数量。
  • 因此用nested结构来处理这种动态kv就比较合适。 nested的本质就是将 {"tags":{"k1":"v1","k2":"v2"}}=>{"tags":[{"key":"key1","value":"v1"},{"key":"key2","value":"v2"}]}
  • 这样一来就可以轻松处理动态kv。并且查询依旧简单,例如k1:v1 AND k2:v2变为
{
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "tags",
            "query": {
              "query_string": {
                "query": "tags.key:k1 AND tags.value:v1"
              }
            }
          }
        },
        {
          "nested": {
            "path": "tags",
            "query": {
              "query_string": {
                "query": "tags.key:k2 AND tags.value:v2"
              }
            }
          }
        }
      ]
    }
  }
}

动态kv如何做agg呢?

  • 普通查询的确很简单,但是agg就并不简单了。原来的模式可以直接用真实字段tags.k1做agg,但是在nested里k1已经变成了一个字段的值,因此没法直接做agg了。
  • 这时就需要引入script大法了。其实agg的本质就是从每个doc的正排里取一个值,用这个值做聚合。因此我们只需要用script遍历list,找到对应的key然后返回其value即可。
  • 简单写了个如下所示,如果有更好的方法欢迎留言。
  • 注意!!! 由于nested单独存储,因此doc里并没有nested数据,需要用params从source中拿。性能很差,仅可用于少量数据场景!
{ ...
  "aggs": {
    "test_agg": {
      "terms": {
        "script": {
          "inline": "for(int i=0;i<params['_source']['tags'].length;i++){if(params['_source']['tags'][i]['key']=='k1'){return params['_source']['tags'][i]['value']}}",
          "lang": "painless"
        },
        "size": 5
      }
    }
  }
}
  • 在使用中我们还可以把script存在来,来加速运算,减少缓存。(注意:5.6以后将code改为了source字段,具体写法参阅文档)
POST _scripts/is_tag_key
{
  "script":{
    "lang": "painless",
    "code":"for(int i=0;i<params['_source']['tags'].length;i++){if(params['_source']['tags'][i]['key']==params.key){return params['_source']['tags'][i]['value']}}"
  }
}
  • 这样用起来就简单多了
{ ...
  "aggs": {
    "test_agg": {
      "terms": {
        "script": {
          "stored": "is_tag_key",
          "params": {
            "key": "k1"
          }
        },
        "size": 5
      }
    }
  }
}

怎么在kibana里做agg呢?

  • kibana其实和上面的一样,也是用script.不过只支持inline的,在script field配置。不过注意一定不能太多,因为每一个inline script都是一个单独的script都需要消耗存储资源。

参考资料

文章来源