S3 イベント通知でオブジェクトキーにコロン (“:”) が含まれるフィルターを指定すると機能しない問題を解決する

2023.08.10

こんにちは、AWS事業本部の平木です!

S3 イベント通知は使っていますか?

S3 バケット内で特定のイベントが発生した場合に通知を受け取る機能ですが、プレフィックスやサフィックスによってフィルタリングできます。
S3 イベント通知をトリガーに Lambda を着火させたいと思った際に、
フィルターの設定において、オブジェクトキーにコロン (":") を含むプレフィックスを設定したら上手く動かなかったため対処した内容をブログにしました。

いきなり結論

コロン (":") のような特別な処理が必要な可能性のある文字については、

  • URL エンコード形式に変換する
    • コロン (":") の場合は、%3A
  • EventBridge への通知をオンにし EventBridge のルールを使用する

以上の2つの方法を使用することで、プレフィックスまたはサフィックスに含めることができます。

背景

構成

下記のようにsample:test/というオブジェクトキーを含む S3 バケットに対して、
ファイルをアップロードした際に Lambda を着火させる構成を使用します。

なにも考えずに S3 イベント通知を設定してみた

まず、比較のためタイプがフォルダのsample/sample:test/を用意しました。

続いてバケットのプロパティからイベント通知の作成を行います。

フィルターがsample/のイベント通知を作成しました。

もちろんですが特殊な条件ではないためsample/sample.txtを、18時13分にアップロードしたところ、
S3 へアップロードされたことをトリガーに Lambda が呼び出され CloudWatch Logs へログが記録されていることが分かります。

続いてsample:test/用のイベント通知を作成してみます。

sample:test/sample.txtを18時18分にアップロードしたしたところ、
Lambda が呼び出されていないことが分かりました。

原因

Lambda が呼び出されない原因を探っていたところ AWS 公式ドキュメントに以下注記がありました。

ワイルドカード文字 (「*」) は、フィルターでプレフィックスまたはサフィックスとして使用することはできません。プレフィックスまたはサフィックスにスペースが含まれている場合は、それを「+」文字に置き換える必要があります。プレフィックスまたはサフィックスの値に他の特殊文字を使用する場合は、URL エンコード (パーセントエンコード) 形式で入力する必要があります。イベント通知のプレフィックスまたはサフィックスに使用する場合で、URL エンコード形式に変換する必要がある特殊文字の一覧については、「セーフ文字」を参照してください。
参照: オブジェクトキー名のフィルタリングを使用したイベント通知の設定 - Amazon Simple Storage Service

この特殊文字についても調べてみました。

AWS 公式ドキュメントにて展開されているオブジェクトキーの命名のガイドラインによると以下3種類に分類されることが分かりました。

安全な文字

一般的にオブジェクトキー名に使用してもよい文字です。

  • 英数字
    • 0 - 9
    • a - z
    • A - Z
  • 以下特殊文字
    • 感嘆符(!)
    • ハイフン(-)
    • アンダースコア(_)
    • ピリオド(.)
    • アスタリスク(*)
    • シングルクォート(')
    • 始め丸括弧(()
    • 終わり丸括弧())

ただし、S3 イベント通知におけるプレフィックスやサフィックスへアスタリスクの使用はできないためご注意ください。

特殊な処理を必要とする可能性がある文字

追加のコード処理や、URL エンコード処理が必要になる可能性のある文字です。

  • アンパサンド ("&")
  • ドル記号 ("$")
  • 16 進数の 00~1F (10 進数の 0~31) の範囲および 7F (10 進数の 127) の ASCII 文字
  • アットマーク ("@")
  • 等号 ("=")
  • セミコロン (";")
  • スラッシュ (/)
  • コロン (":")
  • プラス記号 ("+")
  • スペース – いくつかの用途 (特に複数のスペース) では、スペースの重要なシーケンスが失われる可能性があります。
  • カンマ (",")
  • 疑問符 ("?")

使用しない方がよい文字

相当量の処理が必要になるためキー名には使用しないほうがよい文字です。

  • バックスラッシュ ("\")
  • 左中括弧 ("{")
  • 表示不可能な ASCII 文字 (10 進数の 128 ~ 255 の文字)
  • カレット ("^")
  • 右中括弧 ("}")
  • パーセント記号 ("%")
  • アクサングラーブ/バックティック ("`")
  • 右角括弧 ("]")
  • 引用符
  • 大なり記号 (">")
  • 左角括弧 ("[")
  • チルダ ("~")
  • 小なり記号 ("<")
  • シャープ記号 ("#")
  • 縦棒/パイプ ("|")

以上からプレフィックスやサフィックスに特殊文字を使用する場合には URL エンコード形式に直す必要があるため、
今回コロン (":")をそのまま使ってしまったがために上手く動かなかったということが分かりました。

URL エンコード形式

上記3種類のうち特殊な処理を必要とする可能性がある文字については、
URL エンコード形式に変換することで対処できます。

URL エンコード形式とは、URI 内で有効な限られたUS-ASCII文字のみを使用して、 URI 内の任意のデータをエンコードする方法です。

具体的には下記対応表のように変換します。

変換前 変換後
%20
! %21
" %22
# %23
$ %24
% %25
& %26
' %27
( %28
) %29
* %2A
+ %2B
, %2C
/ %2F
: %3A
; %3B
= %3D
? %3F
@ %40
[ %5B
] %5D

コロン (":") の場合は%3Aのため、
sample:test/sample%3Atest/で表現できます。

コンソールからやってみる

URL エンコード形式で設定

先ほど設定したイベント通知のプレフィックスをsample%3Atest/に設定して保存します。

sample:test/sample2.txtを、11時48分にアップロードしたところ、
無事 Lambda が呼び出されていることがメトリクスとログから分かりました。

Terraform でやってみる

Terraform を使用するとurlencode関数で URL エンコーディングができます。

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = "${aws_s3_bucket.bucket.id}"

  lambda_function {
    lambda_function_arn = "${data.aws_lambda_function.sample_func.arn}"
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "${urlencode("sample:test")}/"
  }
}

全体のサンプルコードは以下です。

コードを展開する

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.37.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

data "aws_lambda_function" "sample_func" {
  function_name = "sample-function"
}

resource "aws_lambda_permission" "allow_bucket" {
  statement_id  = "AllowExecutionFromS3Bucket"
  action        = "lambda:InvokeFunction"
  function_name = "${data.aws_lambda_function.sample_func.arn}"
  principal     = "s3.amazonaws.com"
  source_arn    = "${aws_s3_bucket.bucket.arn}"
}

resource "aws_s3_bucket" "bucket" {
  bucket = "keisuke-poc-tmp"
}

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = "${aws_s3_bucket.bucket.id}"

  lambda_function {
    lambda_function_arn = "${data.aws_lambda_function.sample_func.arn}"
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "${urlencode("sample:test")}/"
  }
}

※コードを参照したい場合は、上の[ ▶ コードを展開する ]を選択してください。

こちらで作成したものを確認すると、

Terraform で作成した S3 バケットとイベント通知が作られているのが分かります。

sample:test/sample.txtを、18時10分にアップロードしたところ、
無事 Lambda が呼び出されていることがメトリクスとログから分かりました。

以上が Terraform で対処する場合の方法でした。

EventBridge で解決する

先ほどまでは URL エンコード形式に変換して対処しましたが、
EventBridge を使用することでコロンをそのまま使用できます。

イベント通知から EventBridge への通知を行うため設定をオンにする必要があります。
イベント通知の Amazon EventBridge にてデフォルトがオフのところをオンにします。

続いて EventBridge にて下記イベントパターンのルールを作成します。
YOUR_BUCKET_NAMEには設定したい S3 バケット名を、プレフィックスは今回sample:test/を記載していますが、
使用される際は設定したいプレフィックスを代入してください。

{
  "source": ["aws.s3"],
  "detail-type": ["Object Created"],
  "detail": {
    "bucket": {
      "name": ["(YOUR_BUCKET_NAME)"]
    },
    "object": {
      "key": [{
        "prefix": "sample:test/"
      }]
    }
  }
}

sample:test/sample1.txtを、18時28分にアップロードしたところ、
無事 Lambda が呼び出されていることがメトリクスとログから分かりました。

参考

終わりに

今回は、S3 イベント通知におけるトラブルに対処しました。
EventBridge を使用することでより高度なフィルタリングができるようになるため、
もし標準の S3 イベント通知の機能に物足りなさを感じる場合は EventBridge を活用いただくとよいかもしれません。
このブログのどなたかの役に立てば嬉しいです。