LoginSignup
9
6

More than 5 years have passed since last update.

Windowsイベントログを標準機能だけでfluentdに突っ込みたい

Last updated at Posted at 2017-09-06

やりたいこと

  • Windowsサーバにソフトウェアをインストールしないでfluentdにログを渡したい
  • ファイルサーバの監査ログをKibanaで気軽に見たい

ハードウェア構成

  • 稼働中のWindowsサーバ7台 (2008R2が3台 + 2012R2が4台) ← あまりいじりたくない
  • 余っているLinuxサーバ1台 (Ubuntu Server 16.04 LTS) ← 自由にいじれる

方針

  • dockerにfluentdとElasticsearch、Kibanaコンテナを立てる
  • fluentdのin_httpプラグインでJSONを受ける
  • PowerShellスクリプトでJSONを生成してfluentdに送信

docker + fluentd + Elasticsearch + Kibana

fluentdのin_httpでJSONを待ち受けるように設定する。

以下のようにdocker-compose.yamlDockerfilefluent.confを配置。
今回はfluentdのin_httpでは9880ポート、Kibanaでは5602ポートを使う。

.
├── docker-compose.yaml
└── fluentd
    ├── Dockerfile
    └── fluent.conf
fluentd/Dockercompose
FROM fluent/fluentd

RUN gem install fluent-plugin-elasticsearch
COPY fluent.conf /fluentd/etc/fluent.conf
fluentd/fluent.conf
<source>
  @type http
  port 9880
  bind 0.0.0.0
  body_size_limit 512m
  keepalive_timeout 10s
</source>
<match **>
  type elasticsearch
  host elasticsearch
  time_key TimeCreated
  port 9200
  logstash_format true
  flatten_hashes true
  flatten_hashes_separator _

  buffer_type file
  buffer_path /tmp/fluentd*.buffer
  buffer_chunk_limit 1024m
#  buffer_queue_limit 256
#  flush_interval 60s
#  retry_wait 5s
</match>
docker-compose.yaml
version: '2'

services:
  fluentd:
    build: ./fluentd
    volumes:
      - windowslog:/var/log/windows
      - fluentdata:/var/log/fluent
    environment:
      FLUENTD_CONF: fluent.conf
    ports:
      - "9880:9880"
    restart: always
    depends_on:
      - elasticsearch

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:5.5.2
    volumes:
      - esdata:/usr/share/elasticsearch/data
      - esconfig:/usr/share/elasticsearch/config
    expose:
      - "9200"
    restart: always
    environment:
      - bootstrap.memory_lock=true
      - xpack.security.enabled=false
      - xpack.monitoring.enabled=false
      - xpack.watcher.enabled=false
      - xpack.graph.enabled=false
      - xpack.ml.enabled=false
      - http.max_content_length=1g
      - thread_pool.index.queue_size=-1
      - thread_pool.bulk.queue_size=-1
      - "ES_JAVA_OPTS=-Xms2048m -Xmx2048m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    mem_limit: 4g

  kibana:
    image: docker.elastic.co/kibana/kibana:5.5.2
    ports:
      - "5602:5601"
    restart: always
    environment:
      - "ELASTICSEARCH_URL=http://elasticsearch:9200"
      - xpack.graph.enabled=false
      - xpack.security.enabled=false
      - xpack.ml.enabled=false
    depends_on:
      - elasticsearch

volumes:
  windowslog:
    driver: local
  fluentdata:
    driver: local
  esdata:
    driver: local
  esconfig:
    driver: local

コンテナ起動。

docker-compose up -d --build

Windowsサーバ側

「ソフトウェアをインストールしない」という要件を満たすため、PowerShellのGet-WinEventを使用する。

Get-WinEventはリモートコンピュータのイベントログも取得できるので、Windowsサーバの内1台にだけスクリプトを設置。ただし、ConvertTo-Jsonを使用するので、PowerShell 3.0以降が入ったWindows 2012R2上で実行する。

下記は1日1回タスクスケジューラで実行することを前提としたスクリプトで、前日の1日分のログを収集してfluentdに送信する。

export_eventlog.ps1
$hosts = @("SERVER1", "SERVER2", "SERVER3", "SERVER4", "SERVER5", "SERVER6", "SERVER7")
$lognames = @("Application", "Security", "System")

$date = Get-Date
$dateend = $date.AddHours(-$date.Hour).AddMinutes(-$date.Minute).AddSeconds(-$date.Second)
$datestart = $dateend.AddDays(-1)

$datestartutc = [System.TimeZoneInfo]::ConvertTimeToUtc($datestart).ToString("yyyy-MM-ddTHH:mm:ssZ")
$dateendutc = [System.TimeZoneInfo]::ConvertTimeToUtc($dateend).ToString("yyyy-MM-ddTHH:mm:ssZ")
$query = "*[System[TimeCreated[@SystemTime>='$datestartutc' and @SystemTime<='$dateendutc']]]"

foreach($h in $hosts){
  foreach($l in $lognames){
    Get-WinEvent -LogName $l -ComputerName $h -FilterXPath $query | % {
      $evt = $_ | ConvertTo-Json -Depth 5 | ConvertFrom-Json
      $evt.TimeCreated = $evt.TimeCreated.ToString("yyyy-MM-ddTHH:mm:ss.fffzzzz")
      $evt.KeywordsDisplayNames = $evt.KeywordsDisplayNames -join " "
      $evt.Properties = ($evt.Properties | % { $_.Value }) -join ' '
      $evt.MatchedQueryIds = $evt.MatchedQueryIds -join " "
      $evt.Bookmark = $evt.Bookmark -join " "
      $evt.Message = $evt.Message -replace "\p{C}+", " "

      Write-Host "$h : $l : $($evt.RecordId)"

      $json = ConvertTo-Json $evt -Compress -Depth 5
      $json = $json -replace "\+", "\u002b"
      $body = [System.Text.Encoding]::UTF8.GetBytes("json=" + $json)
      try{
        $null = Invoke-WebRequest "http://(dockerホストのIP):9880/windows.eventlog" -Method Post -Body $body
      }catch{
        Write-Host $_.Exception.Message -ForegroundColor Red
      }
    }
  }
}

ConvertTo-Jsonでは日付型の値が"\/Date(1326441600000)\/"という形式になってしまいfluentdで解釈できないため、二度手間ではあるがConvertTo-JsonConvertFrom-Jsonとして複製した後、TimeCreateの値をフォーマット。
その他ネストした配列やオブジェクトはfluentdが上手く解釈できないので適当にくっつけておく。
たまにMessage中に不正な文字列が残るらしく、fluentdがエラーを返すので-replace "\p{C}+", " "でUnicode制御文字を消しておく。
URLエンコードしていないのが原因でした。$bodyは下記のようにUrlEncodeしませう。

Add-Type -AssemblyName System.Web

$body = [System.Text.Encoding]::UTF8.GetBytes("json=" + [System.Web.HttpUtility]::UrlEncode($json))

in_httpプラグインの仕様により、"+"が無視されてしまうので、"+""\u002b"エンコードする。

PowerShellの文字列をそのままPOSTすると、非ASCII文字が全部"?"になるので、UTF-8に変換する。

Invoke-WebRequestでfluentdコンテナにJSONをPOSTする。IPはホストのIP、ポートはdocker-compose.yamlで指定した9880ポート。

ある程度まとめて送ってHTTPリクエスト数を減らしたほうが若干高速化するが、下記の理由によりこのスクリプトではイベント1件ずつfluentdに送信している。

  • Get-WinEventに律速されるためあまり高速化しない
  • JSON文字列に不正な文字列が入っていた場合、全件弾かれてしまう

Kibana

ブラウザからhttp://(dockerホストのIP):5602/にアクセス。
時刻に"TimeCreated"を指定してインデックスパターンを作成。

感想

WindowsサーバにRubyなどをインストールしなくても、fluent-plugin-windows-eventlog相当の機能を実現できた。

懸念点はやはり速度で、だいたい数万件/時間でしかログを吐けない。ファイルサーバのアクセス監査ログだと生成速度に追いつけない。

速度のボトルネックはGet-WinEventなので、他にwevtutl.exeを使う方法も検討したが、出力されるXMLに情報が不足している(具体的にはMessageがない)ので断念した。なんとかできないものかなぁ。

9
6
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6