AWS WAFのログで記録されたBlockログのみSyslogサーバに転送する

2022.05.24

はじめに

こんにちは。大阪オフィスの林です。

AWS WAFのログで記録されたBlockログのみSyslogサーバに転送するという検証を行う機会がありましたので、検証した内容をまとめておきたいと思います。
今回やることのイメージとしては下記の通りです。

  • AWS WAFのログをKinesis Data Firehoseに転送する。(下図①の部分)
  • Kinesis Data Firehoseでは全てのログをS3に保存しつつ、Blockログの抽出&加工&Syslogサーバ転送用のLambdaをキックする。(下図②③の部分)
  • Lambda内でBlockログの抽出&加工を行い、Syslogサーバに転送する。(下図④の部分)

なお、LambdaでBlockログとCountログを抽出してKinesis Data Firehose経由でS3バケットに格納するパターンのアーキテクチャは下記ブログにまとまっていますので必要に応じて参照頂ければと思います。

やってみた

Kinesis Data Firehoseの作成&AWS WAFのロギング設定

下記にまんまのブログがありますのでこちらを参照ください。

Syslogサーバの準備

Lambdaからのログ転送を受信するための簡易的なSyslogサーバ(EC2)を構築します。
Syslogの設定には、Amazon Linux 2にデフォルトでインストールされているrsyslogを使っていきます。
/etc/rsyslog.confを見るとデフォルトでは何も受け付けない設定になっています。

[ec2-user@ip-172-31-18-41 ~]$ cat /etc/rsyslog.conf
# rsyslog configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### MODULES ####

# The imjournal module bellow is now used as a message source instead of imuxsock.
$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
$ModLoad imjournal # provides access to the systemd journal
#$ModLoad imklog # reads kernel messages (the same are read from journald)
#$ModLoad immark  # provides --MARK-- message capability

# Provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514

(省略)

ファイルを編集しUDP 514TCP 514を受け付けるように設定変更します。

[ec2-user@ip-172-31-18-41 ~]$ vim /etc/rsyslog.conf
# rsyslog configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### MODULES ####

# The imjournal module bellow is now used as a message source instead of imuxsock.
$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
$ModLoad imjournal # provides access to the systemd journal
#$ModLoad imklog # reads kernel messages (the same are read from journald)
#$ModLoad immark  # provides --MARK-- message capability

# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514

# Provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

(省略)

サービスを再起動します。

[ec2-user@ip-172-31-18-41 ~]$ sudo systemctl restart rsyslog
[ec2-user@ip-172-31-18-41 ~]$

簡易的な設定なので、今回はここまでとします。

Lambdaの作成

次にLambdaを作成していきます。なお、今回はVPC内に構築したSyslogサーバ(EC2)にログを転送する為、VPC Lambdaで作成します。
今回の検証ではランタイムにPython 3.9を使用しています。

import base64
import json
import boto3
import os
import logging
import logging.handlers
import datetime

def lambda_handler(event, context):
  output = []
  output_block = []

  for record in event['records']:
    output_record = {
      'recordId': record['recordId'],
      'result': 'Ok',
      'data': record['data']
    }
    output.append(output_record)

    # pickup log (block)
    a = base64.b64decode(record['data'])
    try:
      b = json.loads(a)
    except ValueError as e:
      pass
    else:
      # Blockログの抽出
      if b['action'] != 'ALLOW':
        print('BLOCK: ' + record['recordId'])
        output_block.append(b)
        # AWS WAFのBlockログから、任意の値を抽出。※今回は日時、アクション、接続元IP、接続元の国、リクエストURIを抽出しています。
        logs = datetime.datetime.fromtimestamp(b["timestamp"]/1000)," , ",b["action"]," , ",b["httpRequest"]["clientIp"]," , ",b["httpRequest"]["country"]," , ",b["httpRequest"]["uri"]
        logs = "".join(map(str, logs))
        
        def initlogger(level = logging.DEBUG):
          logger = logging.getLogger('my-logger')
          logger.setLevel(level)
          # 同じハンドラーが毎回呼び出されるとログが重複してしまったため、ハンドラーが作成されていない場合のみ、ハンドラーが作成されるようにします。
          if not logger.handlers:
            # ハンドラーにSyslogサーバの情報をセットします。
            handler = logging.handlers.SysLogHandler(address = (os.environ['syslogserverip'],514))
            logger.addHandler(handler)
          return logger

        initlogger()
        logger = logging.getLogger('my-logger')
        # Syslogサーバに転送
        logger.warning(" [WAF-BLOCK-LOG] " + logs)

  print('Processed: {} records.'.format(len(event['records'])))

  return {'records': output}

Syslogサーバ(EC2)のIPアドレスは環境変数から呼び出すようにしていますので、Lambdaの環境変数にsyslogserveripというキーでSyslogサーバ(EC2)のIPアドレスを登録しておきます。

Syslogサーバ(EC2)にアタッチしているSecurityGroupにVPC Lambdaからのインバウンド許可を設定したいので、VPC Lambdaには任意のSecurityGroupをアタッチしておきます。※インバウンドの通信は発生しないため設定は不要です。アウトバウンドのみ許可されていれば問題ありません。

Syslogサーバ(EC2)のSecurityGroupでVPC LambdaにアタッチしたSecurityGroupを対象にTCPとUDPの514ポートを許可しておきます。

Kinesis Data Firehoseの設定

Kinesis Data Firehoseの設定で作成したLambdaを指定します。
対象のKinesis Data Firehoseの「Configuration」タブから「Edit」を選択します。

「Data transformation」で「Enable」を選択し作成したLambdaを指定後「Save changes」を選択します。
※Lambdaの実行時間が「3分未満」の場合警告が出ますのでLambdaの実行時間を3分以上に変更しておいてください。

設定できたことを確認します。

動作確認

それでは動作確認をやっていきたいと思います。
まずはAWS WAFでBlockされてみます。

暫くしてSyslogサーバ(EC2)のログを確認すると期待したログが転送されていました。

[ec2-user@ip-172-31-18-41 ~]$ sudo tail -10 /var/log/messages

(省略)

May 23 07:35:11 ip-172-31-29-58.ec2.internal  [WAF-BLOCK-LOG] 2022-05-23 07:33:37.519000 , BLOCK , xxx.xxx.xxx.xxx(実際には接続元のGIPが表示される) , JP , /
May 23 07:35:11 ip-172-31-29-58.ec2.internal  [WAF-BLOCK-LOG] 2022-05-23 07:33:37.886000 , BLOCK , xxx.xxx.xxx.xxx(実際には接続元のGIPが表示される) , JP , /favicon.ico

(省略)

まとめ

Lambdaの加工部分で少し手こずりましたが、期待したログをSyslogサーバに転送できました。
この記事がどなたかの参考になりましたら幸いです。

以上、大阪オフィスの林がお送りしました!