0
0

複数のクロスアカウントでS3イベント通知を構築するCfnテンプレート

Posted at

はじめに

S3バケットにオブジェクトを配置・更新した時に、複数の別のアカウントのLambdaから当該オブジェクトを取得してゴニョゴニョしたい、といったことがありました。
一部、S3の制約に引っかかるなどして手戻りもあったので作業の記録として記事に残します。

本文

S3イベント通知

S3バケットのオブジェクト配置・更新をトリガーにLambdaなどをキックしたい場合、S3のイベント通知を使用することが有効です。

S3オブジェクトのイベントタイプを指定することで、オブジェクトが作成された時・削除された時など、必要に応じてイベントを指定することができます。
また、イベント通知のフィルターを活用することで、特定のファイルに絞り込むことが可能です。
大量のファイルを扱うバケットでも、特定のファイルの特定オブジェクトイベントのみLambdaをキックする、といったことが可能です。

制約

ただ、この通知設定において、フィルターのプレフィックスとサフィックスを指定している、かつ同じイベントタイプを設定しようとすると、エラーとなります。

例えば、「はじめに」に書いたように、特定オブジェクトの更新に対して、Lambdaを起動したいS3通知設定を作成します。

event: s3:ObjectCreated:*
filter prefix: images
filter suffix: jpg
通知先: Lambda Function A

そのうえで、以下のように設定を追加しようとすると、エラーとなります。

event: s3:ObjectCreated:*
filter prefix: images
filter suffix: jpg
通知先: Lambda Function A

event: s3:ObjectCreated:*
filter prefix: images
filter suffix: jpg
通知先: Lambda Function B

無効なプレフィックスやサフィックスの重複がある通知設定の例。を確認すると、

サフィックスが重複していなければ、プレフィックスが重複していても問題ありません。

とあります。ですのでprefixだけ指定していれば、eventが重複していても問題ないのかもしれません。
しかし、今回はsuffixまで厳密に指定したいため、別の方法で対応を考えます。

構成

上記を踏まえて、以下のような構成になります。
qiita021601.png
S3バケットのイベント通知先をSNSトピックにするところがミソになります。
S3の同じ条件のイベント通知を複数のLambdaに送信したい場合、SNSトピックを経由させます。SNSトピックのサブスクリプション先をLambda関数にすることで、複数のLambdaなどのサブスクリプション先にS3イベント通知を送信することができます。

Cfnテンプレート

今回使用したCfnテンプレートを載せておきます。
今回の構成を実現するにはバケットポリシーや、SNSトピックのポリシーなど、複数の権限管理が必要になります。
一部マスクしていますが、それらも網羅しています。

S3バケットを配置するアカウント

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  SnsTopic:
    Type: 'AWS::SNS::Topic'
    Properties:
      TopicName: 'topic'
      DisplayName: 'topic'
  SnsTopicPolicy:
    Type: 'AWS::SNS::TopicPolicy'
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'Subscribe'
            Effect: Allow
            Principal:
              AWS: !Ref Principals
            Action: 'sns:Subscribe'
            Resource: !Ref SnsTopic
          - Sid: 'Publish'
            Effect: Allow
            Principal:
              Service: 's3.amazonaws.com'
            Action: 'sns:Publish'
            Resource: !Ref SnsTopic
            Condition:
              ArnLike:
                'aws:SourceArn': !Sub arn:aws:s3:::<bucket_name>
      Topics:
        - !Ref SnsTopic
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: <bucket_name>
      VersioningConfiguration:
        Status: Enabled
      NotificationConfiguration:
        TopicConfigurations:
          - Event: 's3:ObjectCreated:*'
            Topic: !Ref SnsTopic
            Filter: 
              S3Key:
                Rules:
                  - Name: prefix
                    Value: prefix
                  - Name: suffix
                    Value: suffix
    DependsOn: SnsTopicPolicy
  S3BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Ref Principals
            Action: 's3:GetObject'
            Resource: !Sub arn:aws:s3:::<bucket_name>

送信先のLambdaを管理するアカウント

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  LambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: LambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:GetObject'
                Resource: !Sub arn:aws:s3:::<bucket_name>/*
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: function
      Handler: index.main
      Role: !GetAtt LambdaRole.Arn
      Code:
        ZipFile: 'xxxxxxxxxxxxx'
      Runtime: python3.9
      Timeout: 300
      MemorySize: 128
  LambdaPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !Ref Lambda
      Principal: 'sns.amazonaws.com'
      SourceAccount: !Ref SoureAccountID
      SourceArn: !Sub arn:aws:sns:${AWS::Region}:<source_account_id>:topic
  ExternalSnsTopicSubscription:
    Type: 'AWS::SNS::Subscription'
    Properties:
      Protocol: 'lambda'
      TopicArn: !Sub arn:aws:sns:${AWS::Region}:<source_account_id>:topic
      Endpoint: !GetAtt Lambda.Arn

以下の、SNSサブスクリプションは、送信先のLambdaを配置するアカウント側で実行する必要があります。
(S3バケット側で実行しようとすると、エラーになります。)

  ExternalSnsTopicSubscription:
    Type: 'AWS::SNS::Subscription'
    Properties:
      Protocol: 'lambda'
      TopicArn: !Sub arn:aws:sns:${AWS::Region}:<source_account_id>:topic
      Endpoint: !GetAtt Lambda.Arn

おわりに

S3のイベント通知などを駆使するイベントドリブンな構成は、AWSを利用する上ではよくある構成です。
しかし、ちょっと複雑なことをやろうとするといろいろな制約に引っ掛かり、手戻りなどで思ったより時間がかかってしまいました。

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

Appendix

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