Help us understand the problem. What is going on with this article?

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

概要

いくつかの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が表示されている。

Kibana自体の使い方がよく分からないが、とりあえず、このJSONをインポートすれば誰がException出してるかなどを追えると思う。

kibana.png

[
  {
    "_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\":[]}"
      }
    }
  }
]
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした