作者:吴容,腾讯云 Elasticsearch 高级开发工程师
Elasticsearch 提供了多种数据访问安全的方式,如用户名密码校验、api_key 等。但是依然无法保障数据传输过程中的安全性问题。而 HTTPS 协议,则是一种以安全为目的的 HTTP 通道,在 HTTP 的基础上通过传输加密和身份认证等机制来保障数据传输过程中的安全性。
本文将基于腾讯云 ES 集群环境,演示 Beats、Logstash、Kibana 和 Java Client 等客户端访问连接开启了 HTTPS 协议的 ES 集群。
一、HTTPS 集群环境准备
1、创建 HTTPS 协议集群
首先我们在腾讯云 ES 控制台创建出一个 HTTPS 集群,在购买页这里勾选上 HTTPS 协议。目前该特性目前是通过白名单支持,可提工单申请开放。
图 1. 腾讯云 ES 购买页选择创建 HTTPS 协议的 ES 集群
其中,ES 集群是通过在 elasticsearch.yml 配置文件中设置如下参数来开启 HTTPS 协议的:
 xpack.security.http.ssl.enabled: truexpack.security.http.ssl.keystore.path: certs/ces-certificates.p12xpack.security.http.ssl.truststore.path: certs/ces-certificates.p12
       复制代码
 2、获取 pem 证书文件
集群创建成功后,提工单让腾讯云 ES 研发提供访问 HTTPS 集群所需要的鉴权证书文件。此时腾讯云 ES 侧会提供如下两个证书文件:
下面将详细介绍 Beats、Logstash、Kibana 和 Java 等客户端连接 HTTPS 集群的配置方式。
二、Beats 输出到 HTTPS 集群
1、CVM Metriceat 输出到 ES
我们首先在腾讯云 CVM 控制台创建一个和 ES 集群同 VPC 下的 CVM,创建好后,将得到的 pem 鉴权文件上传到该 CVM 上,这里的存放路径为:/usr/local/service/https-certs。随后到腾讯云 ES 控制台的 Beats 管理页,创建一个 Metricbeat:
图 2. 创建 Metricbeat
其中,最核心的步骤是在 metricbeat.yml 配置文件中进行如下配置。
 output.elasticsearch:  hosts: ["https://ES-VIP:9200"]  username: "elastic"  password: "changeme"  ssl.certificate_authorities: ["/usr/local/service/https-certs/client-certificates.pem"]  ssl.verification_mode: certificate
       复制代码
 
配置完成后,即可在 ES 集群中看到自动创建了一个 metricbeat-7.14.20-*开头的索引,到此 CVM 中 Metricbeat 连接 HTTPS 的 ES 集群配置完成。
图 3. ES 集群中自动创建了 metricbeat 相关的索引
2、TKE Filebeat 日志采集器输出到 ES
TKE Filebeat 日志采集器输出到 HTTPS 的 ES 集群流程和 CVM 的 metricBeat 输出一样,首先我们将 pem 文件上传到创建 TKE 集群时自动创建的 Worker 所在的 CVM 节点上,如/var/log/https-certs 目录下。
 
图 4. 创建 TKE 集群
client-certificates.pem 文件存放位置:
图 5. 拷贝 pem 文件到 Worker 节点
随后我们在腾讯云 ES 控制新建 Fliebeat 采集器,这里选择 TKE 日志采集:
图 6. 创建 Filebeat TKE 日志采集器
下面进行 Filebeat 采集器的基本信息配置,如版本号选择,采集器输出 ES 集群选择,如图 7、8 所示:
 
图 7. 配置 Filbeat 采集器的输出端集群
图 8. 配置 Filebeat 采集 TKE 容器日志集群
创建好 Filebeat TKE 容器日志采集器后,随后我们在 TKE 集群的详情页,找到配置管理中的 ConfigMap,然后找到对应 beats 的 config 文件:
 
图 9. TKE 配置管理页
点击“编辑 yaml”后对 output.elasticsearch 配置项进行修改,如下图 10 所示:
 
图 10. 编辑 output.elasticsearch 配置信息
具体配置信息如下:
 output.elasticsearch:    hosts: ['https://ES-VIP:9200']    username: "elastic"    ssl.certificate_authorities: ["/var/log/https-certs/client-certificates.pem"]    ssl.verification_mode: "certificate"    password: "changeme"    indices:      - index: "filebeat-tke-%{+yyyy.MM.dd}"        when.equals:          tke_collector_target_name: "logs_to_https_es"
       复制代码
 然后在 TKE 集群中的工作负载中,找到 DaemonSet,然后再到对应 beats 的 DaemonSet,点击进去,对它的 pod 进行销毁重建。
 
图 11. TKE DaemonSet 管理页
进入到 Pod 管理页,选择对应的 Pod 销毁重建。
 
图 12. 选择对应 Pod 销毁重建
销毁重建后,Pod 的运行状态变成 Running,如图 13 所示。 
注:如果多次对 pod 进行销毁重建,仍是红色 Running,有可能是 beats 的 yml 配置出现问题,可以在日志里面查看具体问题,或者重新检查一下 yml 配置是否错误。
 
图 13. 销毁重建后的 Pod 正常运行
随后我们到 ES 集群中,可以看到自动创建了以 filebeat-tke-*开头的索引。表明 TKE 日志顺利输出到 HTTPS 集群中了。
 
图 14. ES 集群中自动创建 TKE 日志采集器相关索引
三、Logstash 输出到 HTTPS 集群
腾讯云 Logstash 是一款全托管的产品,因此我们首先需要在腾讯云 Logstash 控制台将 pem 文件以扩展文件方式进行上传,如下图 15 所示。 
图 15. 腾讯云 Logstash 控制台上传 pem 文件
随后我们新建一个管道,在管道的 Config 配置编辑框里配置上 ES 的连接信息和证书路径。
 
图 16. 配置 Logstash 的管道 Config
管道详细配置文本信息如下:
 output {  elasticsearch {      hosts => ["https://ES-VIP:9200"]      user => "elastic"      password => "changeme"      ssl => true      cacert => "/usr/local/service/logstash/extended-files/client-certificates.pem"      ssl_certificate_verification => false  }}
       复制代码
 其中 cacert 即可我们上一步上传的扩展文件 pem 的路径,固定路径为:/usr/local/service/logstash/extended-files/client-certificates.pem。点击保存并部署管道后,就会在管道列表里可以看到我们刚刚新建出来的管道信息了,此时状态为运行中。
图 17. Logstash 管道列表页
这时候我们再到 HTTPS 集群中就可以看到有数据从 input 的集群中写入进来了。
四、Kibana 连接 HTTPS 集群
腾讯云 ES 集群默认自带 Kibana 访问能力,因此一般情况下,客户是不需要对 Kibana 进行任何配置的。这里介绍自建 Kibana 连接 HTTPS 集群的配置方式。和 Beats、Logstash 等使用的客户端鉴权证书不一样,Kibana 使用的是 server-certificates.pem,腾讯云 ES 侧生成证书命令如下:
 openssl pkcs12 -in ces-certificates.p12 -password pass:xxxxxx -nokeys -cacerts -out server-certificates.pem
       复制代码
 由于前面我们已经拿到了 pem 证书文件。因此,我们将 server-certificates.pem 文件拷贝到 Kibana 所在节点的如下路径:/usr/local/service/https-certs。然后修改 kibana.yml 配置文件如下:
 elasticsearch.hosts: ["https://ES-VIP:9200"]elasticsearch.username: "elastic"elasticsearch.password: "changeme"elasticsearch.ssl.verificationMode: certificateelasticsearch.ssl.certificateAuthorities: ["/usr/local/service/https-certs/server-certificates.pem"]xpack.encryptedSavedObjects.encryptionKey: "dfed624ca4014135f61804440536xxxx"xpack.fleet.registryUrl: "https://epr.elastic.co"
       复制代码
 配置项说明:elasticsearch.ssl.certificateAuthorities:pem 文件的存放路径。elasticsearch.ssl.verificationMode:证书鉴权模式,certificate 采用只鉴权 CA 证书,不鉴权主机名称的模式。xpack.fleet.registryUrl:(非必选配置)Fleet 集成模块需要访问的公网仓库,这里需要 Kibana 节点具备公网访问能力。xpack.encryptedSavedObjects.encryptionKey:value 可以通过 bin/kibana-encryption-keys generate 命令获得。 
图 18. 生成 Kibana 安全密钥
重新启动 Kibana 后即可正常访问 HTTPS 的 ES 集群了。
五、Java Client 连接 HTTPS 集群
本文档演示在 Spring 项目中访问 HTTPS 集群的配置方式,首先需要添加如下 maven 依赖:
       <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-client</artifactId><version>7.10.1</version></dependency><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.10.1</version></dependency><dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.10.1</version></dependency>
       复制代码
 随后我们在 ElasticsearchConfig 自定义类中实现对 Elasticsearch 连接的配置。
 
@Configurationpublic class ElasticsearchConfig {  @Value("${es.username}")  private String userName;  @Value("${es.password}")  private String password;  @Value("${es.scheme}")  private String scheme;  @Value("${es.domain}")  private String domain;  @Value("${es.https.cert}")  private String certFile;  @Value("${es.port}")  private int port;  private RestHighLevelClient client;  @Bean  public RestHighLevelClient EsConnectInit() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {      final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();      credentialsProvider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials(userName,password));      RestClientBuilder builder;      if (scheme.equals("https")) {          Path caCertificatePath = Paths.get(certFile);          System.out.println(certFile);          CertificateFactory factory = CertificateFactory.getInstance("X.509");          Certificate trustedCa;          try(InputStream is = Files.newInputStream(caCertificatePath)) {              trustedCa = factory.generateCertificate(is);          }          KeyStore trustStore = KeyStore.getInstance("pkcs12");          trustStore.load(null,null);          trustStore.setCertificateEntry("ca",trustedCa);          SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustStrategy() {              @Override              public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {                  return true;              }          });          final SSLContext sslContext = sslContextBuilder.build();          final HostnameVerifier hostnameVerifier = new HostnameVerifier() {              public boolean verify(String hostname, SSLSession session) {                  return true;              }          };          builder = RestClient.builder(new HttpHost(domain, port, scheme)).setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {              @Override              public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {                  requestConfigBuilder.setConnectTimeout(-1);                  requestConfigBuilder.setSocketTimeout(-1);                  requestConfigBuilder.setConnectionRequestTimeout(-1);                  return requestConfigBuilder;              }          }).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {              @Override              public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {                  httpClientBuilder.disableAuthCaching();                  httpClientBuilder.setSSLHostnameVerifier(hostnameVerifier);                  return httpClientBuilder.setSSLContext(sslContext)                          .setDefaultCredentialsProvider(credentialsProvider);
              }          });      } else {            builder = RestClient.builder(new HttpHost(domain, port, scheme))                  .setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {                      @Override                      public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {                      requestConfigBuilder.setConnectTimeout(-1);                          requestConfigBuilder.setSocketTimeout(-1);                          requestConfigBuilder.setConnectionRequestTimeout(-1);                          return requestConfigBuilder;                      }                  }).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {                      @Override                      public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {                           httpClientBuilder.disableAuthCaching();                          return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);                      }                  });      }      client = new RestHighLevelClient(builder);      return client;  }}
       复制代码
 其中 Elasticsearch 的配置信息如下:
 es.scheme=httpses.port=9200es.domain=9.10.1.X  #es vipes.username=elastices.password=changemees.https.cert=/usr/local/services/certs/client-certificates.pem
       复制代码
 通过如上配置后,即可在 Spring 项目中成功连接上开启了 HTTPS 协议的 ES 集群了。
评论