LoginSignup
11
11

More than 3 years have passed since last update.

複数AWSアカウントのCloudTrailをまとめてKibana(Elasticsearch Service)で可視化する

Last updated at Posted at 2017-03-13

注意点

ここに書かれている設計では、各AWSアカウントのLambdaがログ集約先のElasticsearch Serviceに直接書き込んでいる。
ログの出力量は調整できないのでElasticsearchの書き込み負荷を制御できない。
Elasticsearch Serviceの前段にFirehoseを挟むなどすれば負荷を監視・制御できる(試してない)。
クッションを入れることでElasticsearch Serviceをスケールさせる余裕が生まれるはず。
また、Firehoseを挟むことでLambdaでの変換処理や変換済みデータのS3へのバックアップなど柔軟に対応できる(試してない)。

(参考)
Amazon Kinesis Data Firehose で Amazon Elasticsearch Service 7.x クラスターへのデータストリーム配信をサポート

概要

いくつかのAWSアカウントでCloudTrailの設定を行い、そのログをまとめ用のAWSアカウントに送る。
送られたログは、S3とElasticsearch Serviceに蓄える。
Elasticsearch ServiceのKibanaを利用して可視化する。
まとめ用のAWSアカウントを「管理アカウント」と呼ぶことにする。

手順

  1. 管理アカウントでCloudTrailの設定を行い、S3バケット作成とCloudWatch Logsへの出力を行う(最初のログ)
  2. 管理アカウントでElasticsearch Serviceのドメインを作成する
  3. ドメイン作成後、CloudWatch Logs → (Lambda) → Elasticsearch Serviceの出力設定を行う
  4. その他のアカウントでCloudTrailの設定を行い、管理アカウントのS3バケットと自アカウントのCloudWatch Logsへの出力を行う(2つ目以降のログ)
  5. CloudWatch Logs → (Lambda) → 管理アカウントのElasticsearch Serviceの出力設定を行う
  6. 以降、4→5の手順を繰り返す

管理アカウント

CloudTrailのログを収集しKibanaで可視化するAWSアカウント。
1つ目のログ収集対象アカウントとしてCloudTrailの設定も行う。

CloudTrailの初期設定

Trailの新規作成。
出力先としてS3バケットとCloudWatch Logsを選択する。

S3への出力設定

バケット

アーカイブ用のバケットを新規作成する。

バケットポリシー

CloudTrailサービスに対してバケットへの書き込みを許可する。
ポリシーは自動的に作成される。

bucket-policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSCloudTrailAclCheck20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::BUCKET_NAME"
        },
        {
            "Sid": "AWSCloudTrailWrite20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/AWSLogs/0000000000000/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }
    ]
}

ライフサイクル

バケット作成後、ライフサイクルの設定を行った。
見る機会はあまりないので、最短でStandard-IAに変更するようにした。
とはいえ、最低3ヶ月は検索可能にして、最低1年は残しておいたほうがいいと思う。

  • 30日でStandard-IA
  • 1年でGlacier
  • 3年で削除

CloudWatch Logsへの出力設定

ログをElasticsearch Serviceに流すにあたってCloudWatch Logs Subscriptionを利用するので、CloudTrailのCloudWatch Logs出力を有効にする。

IAMロール

CloudTrailサービスに対してCloudWatch Logsへの書き込みを許可する。
CloudTrail_CloudWatchLogs_Roleロールとポリシーが勝手に作成される。
既存ロールがある場合ポリシーだけ新規作成したり、そこは環境に応じて適当に。

iam-policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AWSCloudTrailCreateLogStream20141101",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream"
      ],
      "Resource": [
        "arn:aws:logs:ap-northeast-1:000000000000:log-group:CloudTrail/DefaultLogGroup:log-stream: 000000000000_CloudTrail_ap-northeast-1*"
      ]
    },
    {
      "Sid": "AWSCloudTrailPutLogEvents20141101",
      "Effect": "Allow",
      "Action": [
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:ap-northeast-1: 000000000000:log-group:CloudTrail/DefaultLogGroup:log-stream: 000000000000_CloudTrail_ap-northeast-1*"
      ]
    }
  ]
}

ロググループ

デフォルトのCloudTrail/DefaultLogGroupにした。

Elasticsearch Serviceのドメイン作成

ドメインを新規作成する。インスタンスタイプやノード数は規模に合わせて適当に。
ここではドメイン名をcloudtrailとした。
あとでARNとエンドポイントURLが必要。

アクセスポリシー

あとで他のアカウントからの書き込み許可も追記するが、とりあえず社内からのアクセスのみ許可するようIP制限をかけた。

access-policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:0000000000:domain/cloudtrail/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "000.000.000.000/32"
        }
      }
    }
  ]
}

インデックステンプレートの作成

デフォルトの設定だと時々 illegal_argument_exception が出るのでインデックス作成時のテンプレートを変更する。
index.mapping.total_fields.limit を3000くらいにするといいらしい。

Limit of total fields [1000] in index [cwl-yyyy.mm.dd] has been exceeded

ついでに apiVersion の型も text に固定する。datetext で混在するとのこと。

参考:【Amazon Elasticsearch Service】CloudTrail、VPC Flowlogsを集約する

設定方法の詳細は公式ドキュメント(IndexTemplates)を参照。
バージョンによって形式に違いがあるので注意。下記の例は5.1の場合。
パスの最後にあるテンプレート名の cloudtrail の部分は何でもよさそう。

$ string trim '
       {
         "template": "cwl-*",
         "settings": {
           "index.mapping.total_fields.limit": 3000
         },
         "mappings": {
           "_default_": {
             "properties": {
               "apiVersion": {
                 "type": "text"
               }
             }
           }
         }
       }' | curl -X PUT -H 'Content-Type: application/json' -d @- 'https://xxx.ap-northeast-1.es.amazonaws.com/_template/cloudtrail'

CloudWatch Logs → Elasticsearch Service

保持期間

CloudTrailのログ保持期間を無期限 → 1週間に変更した。
Elasticsearch Serviceに転送するので、ここでの保持期間は短めにした。

CloudWatch Logs Subscription

CloudTrail/DefaultLogGroupのActionsメニューからStream to Amazon Elasticsearch Serviceを選択すると勝手にLambdaが作成される。

logs_to_es.png

ここでは同一アカウントのドメインを指定する。

choose_es.png

あとはLambdaの実行ロールをlambda_elasticsearch_executionにして、ログ形式からCloudTrailを選択して完了。
LogsToElasticsearch_cloudtrailというLambdaファンクションができているはず。

Elasticsearch Serviceのインデックス掃除

このままだと爆発するのであとで考える。
cwl-YYYY.MM.DDのように日単位でインデックスが作成されるので削除は簡単にできると思う。
.kibanaインデックスが大きくなることはなさそう。

その他のアカウント

管理アカウントにCloudTrailのログを送るアカウント。
CloudTrail、CloudWatch Logs、Lambdaの設定が必要。

CloudTrailの初期設定

Trailの新規作成。
出力先として管理アカウントのS3バケットと自アカウントのCloudWatch Logsを選択する。
ファイルの暗号化はしない、バリデーションはする、に設定した。どちらもデフォルトの設定。

S3への出力設定

バケット

管理アカウントのバケットを指定する。
あらかじめバケットポリシーを設定しておかないと、Trail作成時にエラーになる。

バケットポリシー

管理アカウントのバケットポリシーを編集する。
Resourcearn:aws:s3:::BUCKET_NAME/AWSLogs/1111111111111/*を追記する。
1111111111111は追加対象のAWSアカウントID。

bucket-policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSCloudTrailAclCheck20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::BUCKET_NAME"
        },
        {
            "Sid": "AWSCloudTrailWrite20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME/AWSLogs/0000000000000/*",
                "arn:aws:s3:::BUCKET_NAME/AWSLogs/1111111111111/*"
            ],
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }
    ]
}

CloudWatch Logsへの出力設定

管理アカウントと同様に出力設定を行う。

CloudWatch Logs → Elasticsearch Service

保持期間

管理アカウントと同様に1週間にした。

CloudWatch Logs Subscription

ドメインとして管理アカウントのElasticsearch Serviceを指定する。
ARNとエンドポイントURLが必要。

logs_to_other_es.png

設定完了の時点でlambda_elasticsearch_executionロールからElasticsearch Serviceドメインへのアクセスを許可してないのでエラーになるはず。
なので、lambda_elasticsearch_executionロールが作られた時点で設定完了前に後述のアクセス許可設定を行った。

設定完了後LogsToElasticsearchEx_cloudtrail_000000000000というLambdaファンクションができているはず。

Elasticsearch Serviceへのアクセス許可

アクセスポリシー

管理アカウントのElasticsearch Serviceでアクセスポリシーを変更し、
自分のLambda実行ロール(lambda_elasticsearch_execution)からのes:ESHttpPostアクションを許可する。
アカウントを追加したらPrincipalを配列にすればいいと思う。

access-policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:0000000000:domain/cloudtrail/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "000.000.000.000/32"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::1111111111:role/lambda_elasticsearch_execution"
      },
      "Action": "es:ESHttpPost",
      "Resource": "arn:aws:es:ap-northeast-1:0000000000:domain/cloudtrail/*"
    }
  ]
}

Kibanaの設定

ここまでの設定でKibanaの設定をしてみる。
Kibanaは最初から利用可能になっていて、コンソールにURLが表示されている。

このJSONをインポートすれば誰がException出してるかなどを追えると思う。

ダッシュボードの設定をして、グラフからドラッグで期間を絞ったり、AWSアカウントIDやエラーコードを絞ったあと、その条件をピン留めすることで、クエリー画面に検索条件を持ち越しできる。
クエリー画面では実際のログを詳細に見ることができる。

kibana.png


可視化ダッシュボード用設定のJSON
[
  {
    "_id": "CloudTrail",
    "_type": "dashboard",
    "_source": {
      "title": "CloudTrail",
      "hits": 0,
      "description": "",
      "panelsJSON": "[{\"col\":7,\"id\":\"eventName_AreaChart\",\"panelIndex\":1,\"row\":1,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"col\":4,\"id\":\"Region_PieChart\",\"panelIndex\":2,\"row\":1,\"size_x\":3,\"size_y\":1,\"type\":\"visualization\"},{\"col\":7,\"id\":\"errorCode_AreaChart\",\"panelIndex\":4,\"row\":5,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"userName_TagCloud\",\"panelIndex\":5,\"row\":2,\"size_x\":6,\"size_y\":2,\"type\":\"visualization\"},{\"col\":1,\"id\":\"sessionIssuer.userName_TagCloud\",\"panelIndex\":6,\"row\":4,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"InvokedBy_TagCloud\",\"panelIndex\":7,\"row\":7,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"Owner_PieChart\",\"panelIndex\":8,\"row\":1,\"size_x\":3,\"size_y\":1,\"type\":\"visualization\"}]",
      "optionsJSON": "{\"darkTheme\":true}",
      "uiStateJSON": "{\"P-1\":{\"spy\":{\"mode\":{\"fill\":false,\"name\":null}},\"vis\":{\"legendOpen\":true}},\"P-2\":{\"spy\":{\"mode\":{\"fill\":false,\"name\":null}},\"vis\":{\"legendOpen\":true}},\"P-3\":{\"vis\":{\"legendOpen\":false}},\"P-4\":{\"spy\":{\"mode\":{\"fill\":false,\"name\":null}},\"vis\":{\"legendOpen\":true}}}",
      "version": 1,
      "timeRestore": false,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}}}]}"
      }
    }
  },
  {
    "_id": "eventName_AreaChart",
    "_type": "visualization",
    "_source": {
      "title": "eventName_AreaChart",
      "visState": "{\"title\":\"eventName_AreaChart\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"3\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"eventTime\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"eventName.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Region_PieChart",
    "_type": "visualization",
    "_source": {
      "title": "Region_PieChart",
      "visState": "{\"title\":\"Region_PieChart\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"awsRegion.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "errorCode_AreaChart",
    "_type": "visualization",
    "_source": {
      "title": "errorCode_AreaChart",
      "visState": "{\"title\":\"errorCode_AreaChart\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"eventTime\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"errorCode.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "InvokedBy_TagCloud",
    "_type": "visualization",
    "_source": {
      "title": "InvokedBy_TagCloud",
      "visState": "{\"title\":\"InvokedBy_TagCloud\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":42},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"userIdentity.invokedBy.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "userName_TagCloud",
    "_type": "visualization",
    "_source": {
      "title": "userName_TagCloud",
      "visState": "{\"title\":\"userName_TagCloud\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":38},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"userIdentity.userName.keyword\",\"exclude\":{\"pattern\":\"homes-dev\"},\"size\":15,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "sessionIssuer.userName_TagCloud",
    "_type": "visualization",
    "_source": {
      "title": "sessionIssuer.userName_TagCloud",
      "visState": "{\"title\":\"sessionIssuer.userName_TagCloud\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":12,\"maxFontSize\":24},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"userIdentity.sessionContext.sessionIssuer.userName.keyword\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Owner_PieChart",
    "_type": "visualization",
    "_source": {
      "title": "Owner_PieChart",
      "visState": "{\"title\":\"Owner_PieChart\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"@owner.keyword\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  }
]


11
11
0

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
11
11