S3にパブリックファイルが追加された時にメール通知するLambdaを作成する

スタートプラン

「おれは、こんなファイルを公開するつもりはなかったんや… (T_T)」

昨今、S3オブジェクトの意図しない公開設定により重要機密が漏れるニュースをよく目にします。

AWS側でも事態を重く見ているのか、S3コンソールでパブリック設定されているバケットがあった場合の表示が、派手になりました。「パブリック!!」的な。

今回は、意図しないパブリックオブジェクトが追加された時の防止目的で、S3バケットにパブリックオブジェクトが追加されるたびに、メール通知するLambda関数を作成しました。

LambdaによるS3オブジェクトの処理に汎用的に使える方法なので、直近パブリックオブジェクトを使う予定がない方も参考にしていただければと思います。

 __
(祭) ∧ ∧
 Y  ( ゚Д゚)
 Φ[_ソ__y_l〉     S3パブリック チュウイダワッショイ
    |_|_|
    し'´J

構成と処理のイメージ図

処理の順番

  1. S3バケットにオブジェクトをアップロード
  2. S3バケットに設定していたイベント通知でLambdaを起動
  3. Lambda関数でオブジェクトの公開権限をチェック
  4. もしオブジェクトがPublicであれば、SNSをパブリッシュ
  5. SNSからメール送信

構成の解説

S3には、バケット内で特定のイベントが発生した時に、他のサービスに連携させるためのイベント通知を設定することができます。

Amazon S3 イベント通知の設定 - Amazon Simple Storage Service

利用できるイベントと送信先の一覧はこちら参照。

イベント通知のタイプおよび送信先

今回は、s3.ObjectCreated:系のイベントタイプを利用し、送信先には、Lambdaを指定します。こうすることで、Lambda側のイベントハンドラーで、S3で作成されたオブジェクトの情報を受け取ることができるので、そのオブジェクトの権限を確認し、Publicなら、LambdaからSNSをサブスクライブしメール送信します。

構築手順

今回は、以下の手順で構築していきます。

  1. 対象バケットの作成
  2. メール送信用SNSの作成
  3. Lambda関数の作成
    1. Lambda関数雛形の作成
    2. Lambdaで利用するロールへのACL取得権限付与
    3. Lambda関数コードの作成

手順1「S3バケットの作成」

最初にS3バケットを作成します。この記事では、hamada-public-testとうバケット名を利用します。その他の設定は、一旦全てデフォルトで大丈夫です。

手順2「メール送信用SNSの作成」

メール送信用SNSを作成します。メールが届けばなんでも良いです。作成手順は下記記事をご参照ください。

[基本操作]Amazon SNSでメールを送信する | Developers.IO

SNS作成後、そのARNをメモっておいてください。このARNは、後ほど、LambdaからSNSをパブリッシュする際に利用します。

手順3「Lambda関数の作成」

いよいよLambda関数の作成です。WebコンソールのLambdaを開いて「作成」をクリックし、関数の作成画面を開きます。

手順3-1「Lambda関数雛形の作成」

「一から作成」をクリックし、以下のように入力していきます。

  • 名前:PublicObjectNotifyFunction
  • ランタイム:Python 3.6
  • ロール:テンプレートから新しいロールを作成
  • ロール名:PublicObjectNotifyFunctionRole
  • ポリシーテンプレート:以下の2つを選択
    • 「S3オブジェクトの読み取り専用アクセス権限」
    • 「SNS発行ポリシー」

「関数の作成ボタン」をクリックし、無事、下記画面が表示されたら、OKです。

手順3-2「トリガーの設定」

次に、S3のイベントからこのLambda関数を起動するためのトリガーを作成します。

Lambda画面上、左側のS3をクリックします。

そうすると、トリガーの設定画面が表示されるので、以下の通りに設定していきます。

  • バケット:hamada-public-test
  • イベントタイプ:「オブジェクトの作成」
  • トリガーの有効化:チェックオン

「トリガーの追加」ボタンをクリックし、トリガー部分にS3が追加されればOKです。

手順3-2「Lambdaで利用するロールへのACL取得権限付与」

今回、Lambda作成時にポリシーテンプレートからロールを設定しましたが、S3へのアクセス権限には「S3オブジェクトの読み取り専用アクセス権限」を付与しました。しかしこれでは、オブジェクトの公開権限(Publicかどうか)を取得するためのロールが不足しています。そのため、作成されたIAMロールを修正します

IAMのサービス一覧を開き、先ほど作成した>PublicObjectNotifyFunctionRole</span」をクリックします。

ロールには3つのポリシーが付与されています。ここでは、Lambdaの実行ポリシーに関係する「AWSLambdaS3ExecutionRole-〜」をクリックします。

ポリシーを開いたら、JSONでポリシーを開き、下記Actionに"s3:GetObjectAcl"を追加して保存します。

手順3-3「Lambda関数コードの作成」

いよいよ、最後に、Lambda関数本体を作成します。Lambda関数のコードは以下の通りです。ランタイムのバージョンはPython3.6です。ご注意ください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import urllib
import boto3
 
topic_arn = 'arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:mail-to-cmhamada'
 
 
# 指定オブジェクトのパグリック判定
def isPublicObject(bucket, key):
   
  s3 = boto3.resource('s3')
  obj_acl = s3.ObjectAcl(bucket_name=bucket, object_key=key)
 
  isPublic = False
   
  for grant in obj_acl.grants:
    uri = grant.get('Grantee').get('URI','nothing')
    if 'acs.amazonaws.com/groups/global/AllUsers' in uri:
      isPublic = True
 
  return isPublic
 
# 指定SNSトピックのパブリッシュ
def publishSns(topic_arn,bucket,key):
 
  msg = 'S3バケット「{0}」、オブジェクト「{1}」にパブリック権限が付与されました。'.format(bucket,key)
  subject = "S3にパブリックオブジェクトが追加されました"
  mailclient = boto3.client('sns')
 
  mailrequest = {
    "TopicArn": topic_arn,
    "Message": msg,
    "Subject": subject
  }
   
  response = mailclient.publish(**mailrequest)
 
  return
 
def lambda_handler(event, context):
     
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(str(event['Records'][0]['s3']['object']['key']))
 
    # オブジェクトのPublic権限を判定し、PublicならSNSをパブリッシュ
    if isPublicObject(bucket,key):
      publishSns(topic_arn,bucket,key)
 
    return

利用前、冒頭のtopic_arnに、手順2「メール送信用SNSの作成」で作成したSNSのARNに書き換えてください。

各関数の解説は以下の通り。

  • 「isPublicObject」
    • オブジェクトがPublic権限をもつかどうかを判定します
    • Publicオブジェクトには、ObjectACLのURIにacs.amazonaws.com/groups/global/AllUsersが付与されるため、それで判定しています
    • 詳細はアクセスコントロールリスト (ACL) の概要を参照
  • 「publishSns」
    • 指定したトピックARNに対して、SNSをパブリッシュします
  • 「lambda_handler」
    • Lambda関数のエントリポイント
    • event内に、トリガーとなったS3オブジェクトの情報が含まれているため、その情報をbucketkeyに格納し、他の関数を呼び出します

パブリック権限ファイルのアップロードによる動作確認

AWS CLIを利用して動作確認してみます。オブジェクトアップロード時に、公開権限の有無で動作が違うことを確認します。

Public権限がない時の例(デフォルト)

1
aws s3 cp test.txt  s3://hamada-public-test

Public権限を付与してファイルをアップロード。この時は、何もアラートが上がりません。

1
aws s3 cp test.txt  s3://hamada-public-test --acl public-read

Public権限が付与されたファイルをアップロード時、以下のようなメールが届けば、OKです。

これだとあんまりアラート感ないですが、メール内容や件名は、上のサンプルコードを元にカスタマイズしてみてください。

まとめ「S3イベントを起点にすることでLambdaによる柔軟な処理が可能」

S3イベントを起点にLambdaを起動することで、パブリック権限を確認しメール送信してみました。

今回のサンプルでは、パブリックオブジェクト発見時にはメール送信しているだけですが、他にもパブリック権限をLambda側で取り消すことも可能ですし、逆にオブジェクト毎のメール送信ではなく、DynamoDBあたりにオブジェクトのメタデータを保存しておき、後から一括で処理するなどの方法も可能です。

LambdaにもSNSにも料金が発生するので、あまり大量のS3オブジェクトに対して、全てLambdaを起動するのは気をつけたほうが良いでしょう。予め、料金面を把握しておくことも重要なので、意図せず大きな課金がかからないようにご注意ください。

S3オブジェクトのガバナンスに応用できそうな手法なので、みなさんのS3運用の一手段として取り入れてみてはいかがでしょうか。

それでは、今日はこのへんで。濱田(@hamako9999)でした。

S3パブリック設定関連のリンク集

Developers.IOには、他にもS3パブリック権限周辺の情報が多数あるので、合わせてご参照くださいませ。

S3バケットの公開設定に関する注意喚起など、全般はこちらをご参照ください。

バケットレベルのパブリックチェックであれば、AWS Configを利用することで制御可能です。

S3バケットの中の全オブジェクトに対してパブリック設定を確認するには、以下の方法が有用です。

スタートプラン