S3に集約したログをセキュアに各アカウントのコンソールから閲覧させる方法

記事タイトルとURLをコピーする

こんにちは。AWS CLIが好きな福島です。

はじめに

マルチアカウントが主流である昨今、ログをS3に集約することが多いかと存じますが、 利便性を損なわないよう、各アカウントからもログを確認できるようにしたいというニーズがあるかと思います。

ということで今回は、S3に集約したログをセキュアに各アカウントのコンソールから閲覧させる方法を考えてみました。(各アカウントからAthenaを使った分析も可能です。)

ブログでは、ELBを前提にしていますが、他のサービスでも同様のことができるかと思います。


補足

集約したログを各アカウントから確認する方法は、AWSサービスによって他のアプローチも考えられますため、 要件に合わせて設計することが重要です。 ただ、各アカウントからAthenaで分析したい場合は今回の方法が便利かと存じます。

  • CloudTrail, Config
    • ログを集約しても、各アカウントのCloudTrailやConfigのコンソール画面からログを確認可能
  • VPC および Transit Gateway の Flow Logs
    • ログの出力先を複数設定できるため、S3と合わせて自アカウントのCloudWatch Logsにログを出力する
    • これにより、各アカウントでも直近のログを確認可能

前提

今回はELBログを集約および各アカウントからアクセスすることを想定し、 S3に集約したログを各アカウントからセキュアに閲覧させる方法をご紹介します。

ポイント

  • ELBのログ出力設定において、S3 URIを以下の通りとする
    • s3://<bucket-name>/<AWSアカウントID>/<ELBのログ種>/<ELBのリソース名>
    • アクセス制限を行う上でAWSアカウントIDごとにパスを分けることが肝
    • ELBのログ種, ELBのリソース名は各環境に合わせて設計しても良い
  • S3バケットポリシーで各アカウントから自アカウントのパス配下のオブジェクトのPut,Get,List権限を付与する
    • 以下全てにおいて、aws:SourceOrgIDを利用し、自組織以外のアカウントからの操作を制限する
    • s3:PutObject
      • ${aws:SourceAccount}を利用し、自アカウントのパス配下に対するPut権限を付与する
    • s3:GetObject
      • ${aws:PrincipalAccount}を利用することで各アカウントから自アカウントのパス配下のオブジェクトの閲覧権限を付与する
    • s3:ListBucket
      • s3:prefixかつ${aws:PrincipalAccount}を利用することで各アカウントから自アカウントのパス配下のオブジェクト一覧の閲覧権限を付与する

S3に集約したログを各アカウントからセキュアに閲覧させるバケットポリシー

  • 以下の値は環境に合わせた値に置換します
    • ELB_ACCOUNT_ID: リージョンごとのELBのAWS アカウントID
        • アジアパシフィック (東京) — 582318560864
        • アジアパシフィック (大阪) – 383597477331
        • 米国東部 (バージニア北部) – 127311923021
        • 米国西部 (オレゴン) — 797873946194
    • BUCKET_NAME: S3バケット名
    • ORGANIZATION_ID: アクセス許可する組織のID
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowELBLogsWrite",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::ELB_ACCOUNT_ID:root"
            },
            "Action": "s3:PutObject",
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME/${aws:SourceAccount}/access_logs/*/AWSLogs/*",
                "arn:aws:s3:::BUCKET_NAME/${aws:SourceAccount}/connection_logs/*/AWSLogs/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:SourceOrgID": "ORGANIZATION_ID"
                }
            }
        },
        {
            "Sid": "AllowCrossAccountRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/${aws:PrincipalAccount}/*",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalOrgID": "ORGANIZATION_ID"
                }
            }
        },
        {
            "Sid": "AllowCrossAccountList",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::BUCKET_NAME",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "${aws:PrincipalAccount}/*",
                    "aws:PrincipalOrgID": "ORGANIZATION_ID"
                }
            }
        }
    ]
}

バケットポリシーの解説

Sid: AllowELBLogsWrite

  • ELBからログを出力するための設定になります
  • ログを出力する際のパス構成を統制するため、s3://<bucket-name>/<AWSアカウントID>/<access_logs or connection_logs>/<任意の値>にしかログを出力できないようにしております
  • aws:PrincipalOrgIDを活用し、自組織以外のアカウントからの操作を制限します
## 抜粋
        {
            "Sid": "AllowELBLogsWrite",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::ELB_ACCOUNT_ID:root"
            },
            "Action": "s3:PutObject",
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME/${aws:SourceAccount}/access_logs/*/AWSLogs/*",
                "arn:aws:s3:::BUCKET_NAME/${aws:SourceAccount}/connection_logs/*/AWSLogs/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:SourceOrgID": "ORGANIZATION_ID"
                }
            }
        },

Sid: AllowCrossAccountRead

  • 各アカウントからオブジェクトを閲覧(Get)できる権限設定となります
  • s3://<bucket-name>/<AWSアカウントID>/*と設定することで自アカウントのオブジェクトのみ閲覧できるようにします(他アカウントのELBログの閲覧は許可しない)
  • aws:PrincipalOrgIDを活用し、自組織以外のアカウントからの操作を制限します
## 抜粋
        {
            "Sid": "AllowCrossAccountRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/${aws:PrincipalAccount}/*",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalOrgID": "ORGANIZATION_ID"
                }
            }
        },

Sid: AllowCrossAccountList

  • s3:ListBucketは、Resourceにプレフィックスを含められません(バケットを記載する必要がある)
  • そのため、s3:prefixを利用することで自アカウントのID配下のオブジェクト一覧のみを閲覧できるようにします
  • aws:PrincipalOrgIDを活用し、自組織以外のアカウントからの操作を制限します
# 抜粋
        {
            "Sid": "AllowCrossAccountList",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::BUCKET_NAME",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "${aws:PrincipalAccount}/*",
                    "aws:PrincipalOrgID": "ORGANIZATION_ID"
                }
            }
        }

動作確認

ELBのログ出力(成功)

バケットポリシーに従い、適切なパスを設定し、正しくログを出力できるか確認します。

ログ設定ができたら、正しくログが出力できているか確認します。

正確には、それぞれのパスに「ELBAccessLogTestFile」が出力されていることを確認します。

ELBのログ出力(失敗)

自アカウントと異なるアカウントIDのパスでログ設定をします。

想定通り、権限がない旨のエラーが出ました。

自アカウントのID配下のオブジェクトを閲覧できることを確認

無事に確認できました。

S3バケット直下のパスが閲覧できないことを確認

無事に確認できました。

自アカウント以外のID配下のオブジェクトを閲覧できないことを確認

まずはログを集約するアカウント(S3を所持しているアカウント)にて、S3にテスト用のフォルダを作成します。

集約元となるアカウントからパス指定でアクセスしますが、想定通りアクセスできないことを確認できました。

Athenaからアクセス

Athenaからアクセスすることも可能です。 テーブルを作成する方法は通常と同様でLOCATIONだけ適切な値を設定するだけで問題ないです。 (s3://<bucket-name>/<AWSアカウントID>/<ELBのログ種>/<ELBのリソース名>に適切な値を設定します。)

CREATE EXTERNAL TABLE `alb_access_logs`(
  `type` string COMMENT '', 
  `time` string COMMENT '', 
  `elb` string COMMENT '', 
  `client_ip` string COMMENT '', 
  `client_port` int COMMENT '', 
  `target_ip` string COMMENT '', 
  `target_port` int COMMENT '', 
  `request_processing_time` double COMMENT '', 
  `target_processing_time` double COMMENT '', 
  `response_processing_time` double COMMENT '', 
  `elb_status_code` int COMMENT '', 
  `target_status_code` string COMMENT '', 
  `received_bytes` bigint COMMENT '', 
  `sent_bytes` bigint COMMENT '', 
  `request_verb` string COMMENT '', 
  `request_url` string COMMENT '', 
  `request_proto` string COMMENT '', 
  `user_agent` string COMMENT '', 
  `ssl_cipher` string COMMENT '', 
  `ssl_protocol` string COMMENT '', 
  `target_group_arn` string COMMENT '', 
  `trace_id` string COMMENT '', 
  `domain_name` string COMMENT '', 
  `chosen_cert_arn` string COMMENT '', 
  `matched_rule_priority` string COMMENT '', 
  `request_creation_time` string COMMENT '', 
  `actions_executed` string COMMENT '', 
  `redirect_url` string COMMENT '', 
  `lambda_error_reason` string COMMENT '', 
  `target_port_list` string COMMENT '', 
  `target_status_code_list` string COMMENT '', 
  `classification` string COMMENT '', 
  `classification_reason` string COMMENT '', 
  `conn_trace_id` string COMMENT '')
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.serde2.RegexSerDe' 
WITH SERDEPROPERTIES ( 
  'input.regex'='([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) \"([^ ]*) (.*) (- |[^ ]*)\" \"([^\"]*)\" ([A-Z0-9-_]+) ([A-Za-z0-9.-]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" ([-.0-9]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^ ]*)\" \"([^\\s]+?)\" \"([^\\s]+)\" \"([^ ]*)\" \"([^ ]*)\" ?([^ ]*)?') 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://<bucket-name>/<AWSアカウントID>/<ELBのログ種>/<ELBのリソース名>/'

テーブルを作成後、クエリを実行すると適切に値を確認することができます。

終わり

今回は、S3に集約したログをセキュアに各アカウントのコンソールから閲覧させる方法をご紹介しました。

どなたかのお役に立てれば幸いです。

福島 和弥 (記事一覧)

2019/10 入社

AWS CLIが好きです。