NginxをリバースプロキシにしてWinlogbeatをElasticsearchへ安全に接続してみる（TLS/HTTP Basic Auth）

#はじめに
[Elasticsearch](https://www.elastic.co/jp/products/elasticsearch)は[Beats](https://www.elastic.co/jp/products/beats)との間で暗号通信や認証をもちろんサポートしています（TLSおよび一部クライアント認証が[Elasticsearchのベーシックサブスクリプション](https://www.elastic.co/jp/blog/security-for-elasticsearch-is-now-free)でも無償で利用することができるようになったようです）。

一方で暗号通信処理や認証をWebサービス（この場合Elasticsearch）の前段でリバースプロキシに行わせる構成も柔軟性（障害ポイントの分離・暗号処理オフロード・負荷分散・冗長化・WAF経由通信・クローズドネットワーク構成の隠蔽/抽象化など）や管理の分離（担当者の役割分担など）といった観点で有用な場合も依然としてあります。

本記事はElasticsearchの前段にリバースプロキシとして[Nginx](https://nginx.org/)を配置し、暗号通信/サーバ認証(TLS)およびクライアント認証(HTTP Basic認証)を実行させる構成についての備忘録です。

#利用するソフトウェア
- OS: Ubuntu 18.04
- Nginx(1.14.0)
- XCA(1.4.1)
- Elasticsearch (7.3.2) 
- Kibana (7.3.2)
- Winlogbeat (7.4.0)
- プロキシサーバ: Squid (3.5.27)
- DNSサーバ: Dnsmasq (2.79)
- VMware Workstation Player等のVMソフトウェア
- Windows 10 (クライアントPC)

[Winlogbeat 7.4.2](https://www.elastic.co/jp/downloads/beats/winlogbeat)や[Sysmon](https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon)等のインストールについては本記事ではふれません（[こちらの記事](https://qiita.com/rhpenguin/items/6dc4c3f82cd87122958c)もご参考になれば幸いです）。

#ネットワーク構成例
ここでは以下のようなシンプルなネットワーク構成を想定。
<img width="656" alt="winlogbeat_nginx_rproxy.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/0a617a4d-a460-572d-90da-cac636552dd3.png">
**dmz.example.com**上で動作する[Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html)が全ホストの名前解決を行います。
<img width="723" alt="winlogbeat_nginx_rproxy2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/a4c42b0e-ec42-e6a9-fcce-482645161cdf.png">


クライアントPCからの通信は以下の通りです。

|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|説明|
|---|---|
|Webアクセス|Win10クライアントPC上のWebブラウザはインターネットへdmz.example.comで動作するプロキシサーバ(Squid)経由でアクセス|
ログ転送|Windows 10 PC上で動作するSysmonにより採取されたエンドポイントログをWinlogbeatがElasticsearch(log.example.com)へNginx(esrproxy.example.com, リバースプロキシ)経由で転送。暗号通信/サーバ認証はTLSおよびクライアント認証はHTTP Basic認証|

#作業内容
1. XCAでTLSのサーバ証明書を作成（Elasticsearchの認証用）
2. リバースプロキシ（Nginx）の設定
3. WinlogbeatにTLSおよびHTTP Basic認証の設定

#XCAでサーバ証明書を作成（log.example.comのElasticsearch認証用）
以下は[XCA](https://www.hohnstaedt.de/xca/)を使ってプライベートサーバ証明書を作成する場合の例です。

```text:インストール
# XCAをインストール
$ apt install xca

# 起動
$ xca &
```

以降は[こちらのチュートリアル動画](https://www.hohnstaedt.de/xca/)を参考に作成します。本記事の例では以下のように作成しました。
<img width="614" alt="winlogbeat_nginx_rproxy_xca1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/10a0be41-c704-716c-7917-8ed1f70bf101.png">

### CA (esca.example.com)
<img width="614" alt="winlogbeat_nginx_rproxy_xca2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/163c0964-c3e4-6d9b-f7cc-092f39d806f8.png">
<img width="615" alt="winlogbeat_nginx_rproxy_xca3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/f65daea1-92d0-1bd4-9bec-bfdb777b5f94.png">

### サーバ証明書 (esrproxy.example.com)
<img width="613" alt="winlogbeat_nginx_rproxy_xca4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/ff920362-b3c6-a102-6af7-b459c509c169.png">
<img width="614" alt="winlogbeat_nginx_rproxy_xca5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/7b93dc99-d5e9-49c3-af85-14e86c29f536.png">
SubjectAltNameにリバースプロキシのホスト名を設定しています（**esrproxy.example.com**）。

### 証明書と鍵をエクスポート
CAの証明書、リバースプロキシのサーバ証明書と秘密鍵を[PEM形式](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail)でエクスポートしておきます。

|ファイル名|説明|
|---|---|
|esca.example.com.crt|CA証明書。Winlogbeatへ設定|
|esrproxy.example.com.crt|esrproxy.example.comのサーバ証明書。Nginxへ設定|
|esrproxy.example.com.key|esrproxy.example.comの秘密鍵。Nginxへ設定|

#リバースプロキシ(Nginx)の設定

###HTTP Basic認証用のパスワード設定
Winlogbeatの認証（クライアント認証）用にHTTP Basic認証のユーザ名とパスワードを設定します。

```text
# Apacheのツールを利用します。
$ apt install apache2-utils

# HTTP Basic認証のユーザ名(winlogbeat1)とパスワードを設定
$ htpasswd -c /etc/nginx/.htpasswd winlogbeat1
```

###リバースプロキシの設定
(1) XCAでエクスポートしたリバースプロキシホスト用のサーバ証明書と秘密鍵ファイルをコピーして配置します。

```text
/etc/nginx/certs/esrproxy.example.com.crt
/etc/nginx/certs/esrproxy.example.com.key
```

(2) NginxのElasticsearchとKibana用のリバースプロキシ設定ファイルを**/etc/nginx/sites-available**配下へ作成します。ポート番号はElasticsearchとKibanaのデフォルトポートのままとしておきます。

```config:/etc/nginx/site-available/es-rproxy.conf
# Elasticsearchの設定
server{
    listen         9200;
    server_name    .esrproxy.example.com;

    # TLS(SSL)の設定
    ssl                 on;
    ssl_certificate     /etc/nginx/certs/esrproxy.example.com.crt; # サーバ証明書のパス
    ssl_certificate_key /etc/nginx/certs/esrproxy.example.com.key; # 秘密鍵ファイルのパス

　　#転送用の追加HTTPヘッダ（一応念のため）
    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

    location / {

        #HTTP Basic認証の設定
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd; # htpasswdコマンドで生成したパスワードファイル
   
        # Elasticsearchが動作するlog.example.comへの転送設定
        proxy_pass    http://192.168.2.10:9200;
    }
}

# Kibanaの設定
server{
    listen         5601;
    server_name    .esrproxy.example.com;

    ssl                 on;
    ssl_certificate     /etc/nginx/certs/esrproxy.example.com.crt;
    ssl_certificate_key /etc/nginx/certs/esrproxy.example.com.key;

    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

    location / {
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass    http://192.168.2.10:5601;
    }
}
```

###補足
上述の通り**location/proxy_pass**へElasticsearchやKibanaが動作する**log.example.com**への転送を設定するのですが、以下のようにホスト名の最後を'/'で終わるように書いてしまうとElasticseachへ転送されるURLがNginxによりデコードされてしまい正しく処理されません。

```config:(間違い)
server{
    listen         9200;
    ...
    location / {
        ...
        proxy_pass    http://192.168.2.10:9200/; #最後に'/'をつけてしまう。
    }
}
```
<img width="650" alt="winlogbeat_nginx_rproxy3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/14b6c36a-8660-70d5-2764-077615082f57.png">
[Nginxのマニュアル](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass)によると以下のように記述があります。

```text:引用
...
If proxy_pass is specified without a URI, the request URI is passed to the server 
in the same form as sent by a client when the original request is processed, or 
the full normalized request URI is passed when processing the changed URI:

location /some/path/ {
    proxy_pass http://127.0.0.1;
}
...
```

ということで以下のように設定するとURLがデコードされずそのままElasticsearchへ転送されます。

```config:(正解)
server{
    listen         9200;
    ...
    location / {
        ...
        proxy_pass    http://192.168.2.10:9200; #最後に'/'をつけない。
    }
}
```
<img width="650" alt="winlogbeat_nginx_rproxy4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/f7452133-4b83-efa6-7d5d-c44309379bc1.png">

(3) 設定ファイルのシンボリックリンクを**/etc/nginx/sites-enabled**へ張り有効化します。

```text
$ ln -s /etc/nginx/sites-available/es-rproxy.conf /etc/nginx/sites-enabled/es-rproxy.conf
```

(4) Nginxを再起動します。

```text
$ systemctl restart nginx.service
```


#Winlogbeatを設定

(1) XCAでエクスポートしたCA証明書をコピーして配置します。

```text
C:\Program Files\winlogbeat-7.4.0-windows-x86_64\nginx_rproxy\esca.example.com.crt
```

(2) Elasticsearchへの出力設定にTLS（SSL)の設定を追加します。

```yaml:winlogbeat.yml
...
#================================ Outputs =====================================
# Configure what output to use when sending the data collected by the beat.
#-------------------------- Elasticsearch output ------------------------------
output.elasticsearch:
  # Array of hosts to connect to.
  # Winlogbeatの接続先をリバースプロキシホストへ向けます
  hosts: ["esrproxy.example.com:9200"]

  # Optional protocol and basic auth credentials.
  protocol: "https"
  #クライアント認証(HTTP Basic認証)用のユーザ名とパスワードを設定
  username: "winlogbeat1"
  password: "password"

  # Use SSL settings for HTTPS.
  # TLS(SSL)を有効化
  ssl.enabled: true

  # Configure SSL verification mode. If `none` is configured, all server hosts
  # and certificates will be accepted. In this mode, SSL-based connections are
  # susceptible to man-in-the-middle attacks. Use only for testing. Default is
  # `full`.
  # テストのときにはnoneでまずは試してみてもいいかも。
  ssl.verification_mode: full

  # List of root certificates for HTTPS server verifications
  # リバースプロキシホストのサーバ証明書を認証するCA証明書のパスを指定します
  ssl.certificate_authorities: ["C:\\Program Files\\winlogbeat-7.4.0-windows-x86_64\\nginx_rproxy\\esca.example.com.crt"]
...
```

(3) PowerShellを起動し設定の確認とElasticsearchへの接続確認をしておきます

```text
PS C:\Program Files\winlogbeat-7.4.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
PS C:\Program Files\winlogbeat-7.4.0-windows-x86_64> .\winlogbeat.exe test output
elasticsearch: https://esrproxy.example.com:9200...
  parse url... OK
  connection...
    parse host... OK
    dns lookup... OK
    addresses: 192.168.2.100
    dial up... OK
  TLS...
    security: server's certificate chain verification is enabled
    handshake... OK
    TLS version: TLSv1.2
    dial up... OK
  talk to server... OK
  version: 7.3.2
```

(4) Winlogbeatを再起動します。

```text
PS C:\Program Files\winlogbeat-7.4.0-windows-x86_64> Restart-Service winlogbeat
```

#まとめ
NginxをリバースプロキシにしてElasticsearchのTLSや認証機能を使わずにWinlogbeatからのログ転送を安全に行えるように設定してみました。

（補足）
本当はWinlogbeatの通信は**dmz.example.com**上のSquid（プロキシサーバ）経由で通信したかったのですが・・・Winlogbeat 7.4でうまく動作しなかったので割愛しました。このあたりについては別記事「[Elastic StackのWinlogbeatをソースコードからビルドしてみる](https://qiita.com/rhpenguin/items/726296a906ec04b88745)」にて扱っていますのでご興味のあるかたは参照してみてください。