Redshift監査ログのS3バケットでバケットポリシーを基本Denyにする

Amazon Redshiftの監査ログ機能で、「ログ出力先のS3バケットのアクセス制限をできるだけ絞りたい」という要件を実現するバケットポリシーについて整理していきます。セキュリティ要件的にありがちな設定だとは思うのですが、Conditionの中身が結構複雑になるんですよね。

結論

先にバケットポリシーを掲載します。要点は以下の通りで、★が付いている項目が今回の肝です。このS3バケットを作成したCloudFormationのテンプレートは、本文の最後に掲載しておきます。

  • 明示的な許可
    • Redshift監査ログ出力用アカウント404641285394とユーザーlogsからS3へのアクセス
  • Denyの例外
    • ★Redshift監査ログ出力用アカウント404641285394とユーザーlogsからのアクセス
    • 監査ログバケットを参照できるIAMユーザー or スイッチロール
    • ★Redshiftクラスタにアタッチしている、S3へのアクセス権限があるIAM RoleのID
    • CloudFormationからの制御
{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "Put bucket policy needed for audit logging",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::404641285394:user/logs"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::my-redshift-audit-bucket/*"
        },
        {
            "Sid": "Get bucket policy needed for audit logging",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::404641285394:user/logs"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::my-redshift-audit-bucket"
        },
        {
            "Sid": "MultiRestrictPolicy",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::my-redshift-audit-bucket",
                "arn:aws:s3:::my-redshift-audit-bucket/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:PrincipalArn": [
                        "arn:aws:iam::404641285394:user/logs",
                        "arn:aws:iam::<my-account-id>:role/<my-iam-role>"
                    ],
                    "aws:userId": "AROAXXXXXXXXXXXXXXXXX:*"
                },
                "StringNotEquals": {
                    "aws:CalledVia": "cloudformation.amazonaws.com"
                }
            }
        }
    ]
}

順を追って解説していきます。

Redshift監査ログの基本設定

まず、AWS公式ドキュメントに書いてあることを整理していきます。

Redshift監査ログはデフォルトでは無効です。有効化にはマネジメントコンソールか、AWS CLIでenable_user_activity_loggingを叩く方法があります。前者の方法では、Redshiftクラスタのプロパティタブの編集からEdit audit loggingをクリックします。

有効化のボタンを選択し、監査ログの出力先に既存のS3バケットを使用するか、新しくバケットを作成するか選択します。

既存バケットを使用する際に、S3バケット側で何も設定していないと以下のエラーが表示されるはずです。

ここがなぜエラーになるのかというと、Redshift監査ログはクラスタ自体が出力しているわけでなく、専用AWSアカウントのlogsというユーザーが出力する仕様になっているためです。既存バケットを出力先として設定するためには、バケット側にPutObjectGetBucketAcllogsユーザーに対して許可するバケットポリシーを事前に設定しておく必要があります。logsのアカウントはリージョンごとに異なり、東京(ap-northeast-1)は404641285394、大阪(ap-northeast-3)は090321488786です。詳細は公式ドキュメントをご覧ください。

Database audit logging: Managing log files - Amazon Redshift

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Put bucket policy needed for audit logging",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::404641285394:user/logs"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::BucketName/*"
        },
        {
            "Sid": "Get bucket policy needed for audit logging",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::404641285394:user/logs"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::BucketName"
        }
    ]
}

この監査ログバケットに対して、さらにアクセス制限を加えたいというのが今回の試みです。

基本Denyのバケットポリシーに例外を加えていく

基本Denyにすると以下のようなバケットポリシーとなりますが、これではルートアカウント以外からは誰もアクセスできないバケットができてしまいます。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "Stmt1376526643067",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::404641285394:user/logs"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::my-redshift-audit-bucket/*"
        },
        {
            "Sid": "Stmt137652664067",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::404641285394:user/logs"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::my-redshift-audit-bucket"
        },
        {
            "Sid": "MultiRestrictPolicy",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::my-redshift-audit-bucket",
                "arn:aws:s3:::my-redshift-audit-bucket/*"
            ]
        }
    ]
}

ここにConditionのNot系の条件を加えていくことで、Denyの例外を定義していきます。バケットポリシーの集合関係については下記事が参考になるかと思います。

まずは、先ほどの監査ログ専用AWSアカウントのlogsユーザーをaws:PrincipalArnで除外対象にします。

{
    "StringNotLike": {
        "aws:PrincipalArn": [
            "arn:aws:iam::404641285394:user/logs",
            ...
}

便宜上、監査ログバケットにアクセスできるユーザー(スイッチロール)も用意しておきます。必要なければ加えなくて大丈夫です。

{
    "StringNotLike": {
        "aws:PrincipalArn": [
            "arn:aws:iam::404641285394:user/logs",
            "arn:aws:iam::<my-account-id>:role/<my-iam-role>"
         ],
         ...
}

続いて、Redshiftクラスタから監査ログバケットへのアクセスを除外対象にします。監査ログ検索用にRedshift Spectrumはよく使用されるケースですので、合わせて設定しておきます。監査ログバケットへのアクセス権限を付与したIAMロールをRedshiftクラスタにアタッチし、AROAで始まるそのRole IDをaws:userIdで指定します。この考え方の解説は下記をご覧ください。

{
    "StringNotLike": {
        ...,
         "aws:userId": "AROAXXXXXXXXXXXXXXXXX:*"
    },
    ...
}

最後にCloudFormationからの制御を機能させるために、CloudFormationからの呼び出しをaws:CalledVia で除外対象にします。

{
    "StringNotEquals": {
        "aws:CalledVia": "cloudformation.amazonaws.com"
    }
}

以上でConditionの中身は整いました。バケットポリシーをアタッチして監査ログを有効にした後、Redshift Spectrumで外部テーブルを作成していきます。

Redshift Spectrumから監査ログを確認する

Redshift Spectrumから監査ログを参照するための手順は、AWSの公式ブログで取り上げられているので、こちらを参考にしていきます。

Amazon Redshift Spectrum を使用した監査ログの分析

Redshift Spectrumで使用するIAM Policyをアタッチした後、以下のDDLを順次実行していきます。

-- 外部データベースとスキーマを作成
create external schema s_audit_logs 
from data catalog 
database 'audit_logs' 
iam_role 'arn:aws:iam::<my-account-id>:role/<my-iam-role>'
create external database if not exists
;

-- ユーザーアクティビティログテーブル
create external table s_audit_logs.user_activity_log(
    logrecord varchar(max)
)
 ROW FORMAT DELIMITED 
  FIELDS TERMINATED BY '|' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://my-redshift-audit-bucket/logs/AWSLogs/<my-account-id>/redshift/ap-northeast-1/'
;

-- 接続ログテーブル
CREATE EXTERNAL TABLE s_audit_logs.connections_log(
  event varchar(60),  recordtime varchar(60), 
  remotehost varchar(60),  remoteport varchar(60), 
  pid int,  dbname varchar(60), 
  username varchar(60),  authmethod varchar(60), 
  duration int,  sslversion varchar(60), 
  sslcipher varchar(150),  mtu int, 
  sslcompression varchar(70),  sslexpansion varchar(70), 
  iamauthguid varchar(50),  application_name varchar(300))
ROW FORMAT DELIMITED 
  FIELDS TERMINATED BY '|' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://my-redshift-audit-bucket/logs/AWSLogs/<my-account-id>/redshift/ap-northeast-1/'
;

バケットに対して監査ログを有効にした後、最初のログ出力まで1~2時間はかかるので、しばし待ってから確認することにします。

SELECT * FROM s_audit_logs.user_activity_log LIMIT 10;
SELECT * FROM s_audit_logs.connections_log LIMIT 10;

無事、Redshiftから監査ログを取得することができました。

CloudFormationのテンプレート

最後に、今回のバケット作成に使用したCloudFormationのテンプレートを載せておきます。他にIPやVPCエンドポイントで除外条件を加えたい場合は、このテンプレートで色々試してみてください。

AWSTemplateFormatVersion: 2010-09-09
Description: Access Restricted S3 Bucket
Parameters:
  S3BucketName:
    Type: String
    Default: my-redshift-audit-bucket
Resources:
  SampleBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub ${S3BucketName}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  SampleBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref SampleBucket
      PolicyDocument:
        Statement:
          - Action:
            - s3:PutObject
            Effect: Allow
            Sid: 'Put bucket policy needed for audit logging'
            Resource: !Sub 'arn:aws:s3:::${SampleBucket}/*'
            Principal:
              AWS: arn:aws:iam::404641285394:user/logs
          - Action:
            - s3:GetBucketAcl
            Effect: Allow
            Sid: 'Get bucket policy needed for audit logging'
            Resource: !Sub 'arn:aws:s3:::${SampleBucket}'
            Principal:
              AWS: arn:aws:iam::404641285394:user/logs
          - Action:
            - 's3:*'
            Effect: Deny
            Sid: MultiRestrictPolicy
            Resource: 
              - !Sub 'arn:aws:s3:::${SampleBucket}'
              - !Sub 'arn:aws:s3:::${SampleBucket}/*'
            Principal: '*'
            Condition:
              StringNotLike:
                'aws:userId':
                  - "AROAXXXXXXXXXXXXXXXXX:*"
                'aws:PrincipalArn':
                  - arn:aws:iam::404641285394:user/logs
                  - arn:aws:iam::<my-account-id>:role/<my-iam-role>
              StringNotEquals:
                "aws:CalledVia":
                  - "cloudformation.amazonaws.com"

Outputs:
  SampleBucket:
    Value: !Ref SampleBucket