VPCフローログをAmazon Athenaで分析する

「VPCフローログ」をS3に出力して「Amazon Athena」で分析する方法についてご紹介します。
2020.03.19

みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。

今回は、VPCフローログ をS3に出力して、Amazon Athena で分析する方法についてご紹介します。

VPCフローログとは

VPC内のネットワークトラフィックをキャプチャ (捕捉) して、ログに保存する機能です。

フローログを設定できるリソース は以下の通りです:

  • VPC
  • サブネット
  • ネットワークインターフェイス (ENI)

どのポイントに設定した場合でも、ネットワークトラフィックのキャプチャが行われるのは「ネットワークインターフェイス (ENI)」が対象となります。
(VPCやサブネットに対してフローログを設定した場合は、VPCやサブネット内に存在する全てのネットワークインターフェイスが対象になります)

ネットワークインターフェイスが対象ということは、EC2インスタンスやELB、RDSなどに出入りするパケットを可視化できるということですね。

フローログの出力先 に指定できるのは以下のサービスです:

  • CloudWatch Logs
  • S3バケット

ネットワークトラフィックの特定のアクセス傾向を検知してアラームを発する場合には、CloudWatch Logsへ出力します。

あるいは、今回のようにフローログを蓄積してAthenaで分析する場合にはS3バケットへ出力する、というように使い分けを行うのが良いでしょう。

設定方法

1. VPCフローログをS3へ出力するように設定する

AWSドキュメントの内容に沿って設定していきます。

Amazon S3 へのフローログの発行 - Amazon Virtual Private Cloud

まず、フローログの出力先となるS3バケットを作成しておきます。

準備ができましたら、フローログの設定を行います。
今回はVPCに対してフローログを設定します。

マネジメントコンソールで対象VPCの「フローログ」タブを選択して、「フローログの作成」をクリックします。

フローログの設定を入力します。

フィルタ
・「承諾」: 通過を許可されたパケットのみ記録
・「却下」: 通過を拒否されたパケットのみ記録
・「すべて」: 「承諾」「却下」の両方のパケットを記録
今回は「すべて」を選択しました
送信先
「S3バケットへの送信」を選択します
S3バケットARN
送信先S3バケットのARNを指定します (例:arn:aws:s3:::bucket_name)
Log record format
フローログに記録される項目について、デフォルトのままか、カスタマイズするのかを選択します

フローログに記録される各項目については、下記リンク先を参照してください。
フローログレコード

※ 2020年3月18日のアップデートで、VPCフローログに「タグ」が設定できるようになっています。
[アップデート] VPC Flow Logs にタグ付けできるようになりました | Developers.IO

なお、フローログの作成を行うと、保存先に指定したS3バケットに対して自動的に バケットポリシー が設定されます。
設定されるバケットポリシーの例:

{
    "Version": "2012-10-17",
    "Id": "AWSLogDeliveryWrite20150319",
    "Statement": [
        {
            "Sid": "AWSLogDeliveryWrite",
            "Effect": "Allow",
            "Principal": {
                "Service": "delivery.logs.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::cm-demo-vpc-flow-logs/AWSLogs/123456789012/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        },
        {
            "Sid": "AWSLogDeliveryAclCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": "delivery.logs.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::cm-demo-vpc-flow-logs"
        }
    ]
}

サービス delivery.logs.amazonaws.com に対して必要なアクセス権限が許可されていることが確認できます。

通常は自動で設定されたポリシーを変更する必要はありませんが、アカウントまたぎなど特殊なアクセス権限が必要な場合にはポリシーを修正してください。

2. S3バケットにフローログが記録されることを確認する

フローログを作成してしばらく経つと (数分~数十分)、S3バケット内にログが保存され始めます。

ログの保存先は、以下のようなフォルダ階層となっています。
{保存先バケット}/AWSLogs/{アカウントID}/vpcflowlogs/{リージョン名}/YYYY/MM/DD/

ログファイル名は以下の命名規則となります。
{アカウントID}_vpcflowlogs_{リージョン名}_{フローログID}_{タイムスタンプ}_{ハッシュ値}.log.gz

なお、ネットワークインターフェイスにトラフィックが発生しない限りはログが保存されません。
「いつまで経ってもログが記録されない!?」という場合には、VPC内でトラフィック対象のリソースが起動しているかどうか確認しましょう。

試しに、ログファイルをダウンロードして、アーカイブ (tar.gz形式) を解凍して、中身を見てみましょう。

version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status
2 123456789012 eni-0c8d91464ef82783f 212.210.xx.xx 192.168.0.234 15056 22 6 1 40 1584474297 1584474304 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 98.232.xx.xx 192.168.0.234 10313 22 6 1 40 1584474297 1584474304 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 182.141.xx.xx 192.168.0.234 30373 22 6 1 40 1584474297 1584474304 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 117.3.xx.xx 192.168.0.234 61166 23 6 1 44 1584474297 1584474304 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 80.82.xx.xx 192.168.0.234 53225 4567 6 1 40 1584474312 1584474313 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 198.108.xx.xx 192.168.0.234 20089 623 6 1 40 1584474334 1584474334 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 98.14.xx.xx 192.168.0.234 43723 22 6 1 40 1584474357 1584474358 REJECT OK
2 123456789012 eni-0c8d91464ef82783f 209.17.xx.xx 192.168.0.234 59233 3000 6 1 44 1584474365 1584474376 REJECT OK
・・・

今回はVPC内にEC2インスタンスを1台起動して様子を見たのですが (もちろんセキュリティグループはちゃんと閉じています)、あっと言う間に見知らぬIPアドレスからSSH (22/TCP) のアクセスがあり、却下 (REJECT) されたことが記録されていました。

3. Athenaの外部テーブルを定義する

S3バケットに保存されたフローログをAthenaで分析できるようにします。

こちらも、AWSドキュメントの内容に沿って設定していきます。

Amazon VPC フローログのクエリ - Amazon Athena

AWSドキュメントの手順にはありませんが、テーブルを作成する前に「データベース」を作成しましょう。

マネジメントコンソールでAmazon Athenaの「クエリエディタ (Query Editor)」を開き、クエリ欄に以下の通り入力します。

CREATE DATABASE VPCFlowLogs;

「Run query」をクリックしてクエリを実行します。
Results欄に「Query successful.」と表示されれば正常にデータベースが作成されました。

左上の「Database」プルダウンから作成されたデータベースを選択します。

続いて、テーブルを作成するクエリを実行します。
S3バケットというAthenaの「外部」のデータソースを参照するため、外部テーブルを作成する CREATE EXTERNAL TABLE 文を用います。

CREATE EXTERNAL TABLE IF NOT EXISTS vpc_flow_logs (
  version int,
  account string,
  interfaceid string,
  sourceaddress string,
  destinationaddress string,
  sourceport int,
  destinationport int,
  protocol int,
  numpackets int,
  numbytes bigint,
  starttime int,
  endtime int,
  action string,
  logstatus string
)
PARTITIONED BY (dt string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ' '
LOCATION 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/'
TBLPROPERTIES ("skip.header.line.count"="1");

テーブルの「列」は、フローログの作成時に指定した項目 (今回はデフォルトのまま) と1対1で対応するようにします。(列と項目の名前は一致している必要はありません)

PARTITIONED BY 句でテーブルに「パーティション分割」を指定します。
これは、ログデータの読み込み単位を日付単位で分割することにより、クエリ実行の速度向上や費用削減を図ることを目的としています。
(パーティション分割に関する詳細は後ほど説明します)

LOCATION 句にS3バケット内のフォルダへのパスを記述します。(日付部分である YYYY/MM/DD/ は含みません)
S3バケット名、アカウントID、リージョンは適宜書き換えてください。

「Run query」をクリックしてクエリを実行します。
Results欄に「Query successful.」と表示され、左側の「Table」欄に作成したテーブルが表示されていればOKです。

4. テーブルにパーティションを追加してデータを取り込む

この時点では、まだテーブルに実際のログデータは取り込まれていません。

テーブル作成時に「パーティション分割」を指定しましたが、具体的なパーティションを追加してあげることで対象のデータをテーブルにロードします。

具体的には以下のようなクエリを実行します。

ALTER TABLE vpc_flow_logs
ADD PARTITION (dt='2020-03-17')
location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2020/03/17';

これは、S3バケットのパス ~/2020/03/17/ 内のデータを、Athenaテーブルの dt='2020-03-17' で指定されるパーティションに紐付けることを意味します。
(紐付けと同時にデータのロードが行われます)

「日付が変わるたびに上記のようなクエリを毎回実行するのは面倒だ」という場合は、「予め将来分のクエリをまとめて実行しておく」という方法もあります。

パーティションの追加は、まだ存在しないS3パスを指定して行うことが可能です。
例えば以下のシェルスクリプトは、向こう1年分のパーティションを追加するSQLクエリを生成します。

#!/bin/sh
START_DATE=2020-04-01
END_DATE=2021-03-31
TABLE_NAME=vpc_flow_logs
LOG_PATH=s3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1

CUR_DATE=$START_DATE
echo "ALTER TABLE $TABLE_NAME ADD"
while :
do
  SUB_DIR=`date -d "$CUR_DATE" "+%Y/%m/%d"`
  echo -n "PARTITION (dt="\'"$CUR_DATE"\'") location "\'"$LOG_PATH/$SUB_DIR/"\'
  if [ $CUR_DATE = $END_DATE ]; then
    echo ";"
    break
  else
    echo
  fi
  CUR_DATE=`date -d "$CUR_DATE 1 day" "+%Y-%m-%d"`
done

実行結果:

ALTER TABLE vpc_flow_logs ADD
PARTITION (dt='2020-04-01') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2020/04/01/'
PARTITION (dt='2020-04-02') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2020/04/02/'
PARTITION (dt='2020-04-03') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2020/04/03/'
PARTITION (dt='2020-04-04') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2020/04/04/'
PARTITION (dt='2020-04-05') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2020/04/05/'
・・・
PARTITION (dt='2021-03-30') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2021/03/30/'
PARTITION (dt='2021-03-31') location 's3://cm-demo-vpc-flow-logs/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/2021/03/31/';

一時的な調査などで特定日付のフローログをAthenaで分析したい場合は、日付単位でパーティションを追加すれば十分だと思います。

一方、恒常的にフローログのAthenaに取り込んで分析を行うような場合は、まとめてパーティションを追加しておくのが良いでしょう。

パーティション分割の詳細については、AWSドキュメントの以下ページを参照してください。
データのパーティション分割 - Amazon Athena

また、以下のページで ALTER TABLE ADD PARTITION 文、ALTER TABLE DROP PARTITION 文、SHOW PARTITIONS 文の構文を確認しておくと良いでしょう。
DDL ステートメント - Amazon Athena

5. ログデータに対してクエリを行う

これでログデータの分析を行う準備ができましたので、いくつか試してみましょう。

(例1) 期間を指定してログを抽出

クエリ:

SELECT
  from_unixtime(starttime, 9, 0) AS starttime_jst,
  from_unixtime(endtime, 9, 0) AS endtime_jst,
  interfaceid,
  sourceaddress,
  destinationaddress,
  sourceport,
  destinationport,
  protocol,
  numpackets,
  numbytes,
  action,
  logstatus
FROM vpc_flow_logs
WHERE from_unixtime(starttime, 9, 0) >= cast('2020-03-18 12:00:00 +09:00' as timestamp with time zone)
  AND from_unixtime(starttime, 9, 0) <  cast('2020-03-18 13:00:00 +09:00' as timestamp with time zone)
ORDER BY starttime;

実行結果:

starttime_jst                   endtime_jst                     interfaceid            sourceaddress  destinationaddress  sourceport  destinationport  protocol  numpackets  numbytes  action  logstatus
------------------------------  ------------------------------  ---------------------  -------------  ------------------  ----------  ---------------  --------  ----------  --------  ------  ---------
2020-03-18 12:00:01.000 +09:00  2020-03-18 12:00:02.000 +09:00  eni-0c8d91464ef82783f  158.101.xx.xx  192.168.0.234       7070        22               6         1           40        REJECT  OK
2020-03-18 12:00:01.000 +09:00  2020-03-18 12:00:02.000 +09:00  eni-0c8d91464ef82783f  182.231.xx.xx  192.168.0.234       3123        22               6         1           40        REJECT  OK
2020-03-18 12:00:26.000 +09:00  2020-03-18 12:00:26.000 +09:00  eni-0c8d91464ef82783f  182.84.xx.xx   192.168.0.234       11873       22               6         1           40        REJECT  OK
2020-03-18 12:00:40.000 +09:00  2020-03-18 12:00:47.000 +09:00  eni-0c8d91464ef82783f  158.214.xx.xx  192.168.0.234       62721       22               6         1           40        REJECT  OK
2020-03-18 12:00:40.000 +09:00  2020-03-18 12:00:47.000 +09:00  eni-0c8d91464ef82783f  182.12.xx.xx   192.168.0.234       34105       22               6         1           40        REJECT  OK
2020-03-18 12:00:40.000 +09:00  2020-03-18 12:00:47.000 +09:00  eni-0c8d91464ef82783f  158.21.xx.xx   192.168.0.234       8250        22               6         1           40        REJECT  OK
2020-03-18 12:00:53.000 +09:00  2020-03-18 12:00:55.000 +09:00  eni-0c8d91464ef82783f  122.117.xx.xx  192.168.0.234       11676       23               6         1           40        REJECT  OK
2020-03-18 12:00:53.000 +09:00  2020-03-18 12:00:55.000 +09:00  eni-0c8d91464ef82783f  201.218.xx.xx  192.168.0.234       50688       445              6         1           52        REJECT  OK
2020-03-18 12:01:18.000 +09:00  2020-03-18 12:01:18.000 +09:00  eni-0c8d91464ef82783f  98.128.xx.xx   192.168.0.234       8353        22               6         1           40        REJECT  OK
2020-03-18 12:01:26.000 +09:00  2020-03-18 12:01:26.000 +09:00  eni-0c8d91464ef82783f  209.141.xx.xx  192.168.0.234       48562       8088             6         1           44        REJECT  OK
・・・
2020-03-18 12:59:25.000 +09:00  2020-03-18 12:59:32.000 +09:00  eni-0c8d91464ef82783f  45.76.xx.xx    192.168.0.234       123         42753            17        1           76        ACCEPT  OK
2020-03-18 12:59:25.000 +09:00  2020-03-18 12:59:32.000 +09:00  eni-0c8d91464ef82783f  192.168.0.234  45.76.xx.xx         42753       123              17        1           76        ACCEPT  OK
2020-03-18 12:59:49.000 +09:00  2020-03-18 12:59:49.000 +09:00  eni-0c8d91464ef82783f  223.71.xx.xx   192.168.0.234       59286       1022             6         1           44        REJECT  OK

(例2) 拒否されたパケットのアクセス元IPアドレスの上位10個

クエリ:

SELECT sourceaddress, COUNT(*) AS num_of_access
FROM vpc_flow_logs
WHERE action='REJECT'
GROUP BY sourceaddress
ORDER BY num_of_access DESC
LIMIT 10;

実行結果:

sourceaddress  num_of_access
-------------  -------------
195.231.xx.xx  193
209.141.xx.xx  113
104.244.xx.xx  103
185.156.xx.xx  48
195.54.xx.xx   44
185.156.xx.xx  42
92.63.xx.xx    41
83.97.xx.xx    38
185.156.xx.xx  37
92.118.xx.xx   35

おわりに

VPCフローログをS3バケットに出力する方法、S3バケットに保存されたフローログをAmazon Athenaを使って分析する方法についてご紹介しました。

セキュリティ問題やネットワークトラブルが発生した際など、大量に出力されるVPCフローログから必要な情報を得るために、VPCフローログのAthenaによる分析が有効だと思います。
必要が生じた時にスムーズにAthenaが使えるよう、実際に設定してみて確かめておくと良いのではないでしょうか。