こしあん
2025-01-03

S3 Object Lambdaを試す


8{icon} {views}


TerraformとAWSのS3 Object Lambdaを連携させることで、S3バケットのオブジェクトを動的に加工しながら取得する仕組みを構築します。JSONデータのnameキーをLambdaでマスクする例を示し、追加のデータ複製なしにセキュアなデータ取得を実現する手順を解説します。

はじめに

  • DVAやDEAの勉強していたときに、「PIIが入っているバケットに対して特定のキーをマスクして取得したいときは、S3 Object Lambdaが便利だよ」ってあったので試してみた

S3 Object Lambdaとは

S3 Object Lambda を使用したオブジェクトの変換より図を引用。

S3の前段にObject Lambdaアクセスポイントを作り、その背後にLambdaを動かして、S3の構造は維持しつつ追加のデータの複製なしで、オブジェクトの内容を動的に編集して取得するというもの。

作るもの

どの程度使えるのかよくわからないので、サクッとChatGPTにTerraformを生成させて作ってみる。以下のようなディレクトリ構造。

.
├── main.tf
└── s3_object_lambda.py

Terraformのコード

# 1. S3バケットの作成
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-s3-object-lambda-bucket-123456" # ユニークな名前に変更してください
  force_destroy = true
}

# 2. JSONファイルのアップロード
resource "aws_s3_object" "object1" {
  bucket = aws_s3_bucket.my_bucket.id
  key    = "object1.json"
  content_type = "application/json"
  content = jsonencode({
    name = "Alice"
    age  = 30
  })
}

resource "aws_s3_object" "object2" {
  bucket = aws_s3_bucket.my_bucket.id
  key    = "object2.json"
  content_type = "application/json"
  content = jsonencode({
    name = "Bob"
    age  = 25
  })
}

# 3. Lambda用のIAMロールの作成
resource "aws_iam_role" "lambda_role" {
  name = "s3_object_lambda_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "lambda.amazonaws.com"
      }
    }]
  })
}

# 4. Lambda用のIAMポリシーの作成
resource "aws_iam_role_policy" "lambda_policy" {
  name = "s3_object_lambda_policy"
  role = aws_iam_role.lambda_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:GetObject",
          "s3:ListBucket"
        ]
        Effect   = "Allow"
        Resource = [
          aws_s3_bucket.my_bucket.arn,
          "${aws_s3_bucket.my_bucket.arn}/*"
        ]
      },
      {
        Action = [
          "s3-object-lambda:WriteGetObjectResponse"
        ]
        Effect   = "Allow"
        Resource = "*"
      },
      {
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Effect   = "Allow"
        Resource = "arn:aws:logs:*:*:*"
      }
    ]
  })
}

# 5. Lambda関数のZIPアーカイブを作成
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_file = "s3_object_lambda.py"
  output_path = ".cache/s3_object_lambda.zip"  
}

# 6. Lambda関数の作成
resource "aws_lambda_function" "mask_function" {
  function_name = "s3-object-lambda-mask"

  role          = aws_iam_role.lambda_role.arn
  handler       = "s3_object_lambda.handler"
  runtime       = "python3.12"

  filename         = data.archive_file.lambda_zip.output_path
  source_code_hash = filebase64sha256(data.archive_file.lambda_zip.output_path)

  memory_size = 128
  timeout     = 30
}

# 7. サポート用のS3アクセスポイントの作成
resource "aws_s3_access_point" "supporting_ap" {
  bucket = aws_s3_bucket.my_bucket.id
  name   = "supporting-ap"
}

# 8. S3 Object Lambda Access Pointの作成
resource "aws_s3control_object_lambda_access_point" "object_lambda_ap" {
  name = "my-object-lambda-ap"

  configuration {
    supporting_access_point = aws_s3_access_point.supporting_ap.arn

    transformation_configuration {
      actions = ["GetObject"]

      content_transformation {
        aws_lambda {
          function_arn = aws_lambda_function.mask_function.arn
        }
      }
    }
  }
}

# 9. Lambda関数をS3 Object Lambdaから呼び出すための権限を付与
resource "aws_lambda_permission" "allow_s3_object_lambda" {
  statement_id  = "AllowS3ObjectLambdaInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.mask_function.function_name
  principal     = "s3-object-lambda.amazonaws.com"
  source_arn    = aws_s3control_object_lambda_access_point.object_lambda_ap.arn
}

以下メモ

  • Lambdaを作る部分は普通と一緒だが、追加にs3-object-lambda:WriteGetObjectResponseのポリシーが必要。これは変換したオブジェクトをS3に返す際に必要になる
  • S3用のアクセスポイントaws_s3_access_pointaws_s3control_object_lambda_access_pointを作る。本来必要なのはObject Lambdaアクセスポイントだが、これを作成するのにS3のアクセスポイントが必要なため。
  • EventBridgeなどと同様に、S3 Object LambdaからLambdaが呼び出されることを許可する

Lambdaの画面から、設定→アクセス権限と見ると、「リソースベースのポリシーステートメント」に追加されている。

Lambda

import json
import urllib.request
import boto3

def handler(event, context):
    s3 = boto3.client("s3")

    # getObjectContext から必要な情報を取得する
    request_route = event["getObjectContext"]["outputRoute"]
    request_token = event["getObjectContext"]["outputToken"]

    # getObjectContextから情報を取得
    get_object_context = event['getObjectContext']
    input_s3_url = get_object_context['inputS3Url']

    # S3オブジェクトを取得
    with urllib.request.urlopen(input_s3_url) as response:
        data = response.read().decode('utf-8')

    # JSONデータをロード
    json_data = json.loads(data)

    # 'name'をマスク
    if 'name' in json_data:
        json_data['name'] = '***MASKED***'

    # JSONデータを文字列に変換し、バイナリに変換
    json_bytes = json.dumps(json_data).encode("utf-8")

    # s3.write_get_object_response でレスポンスを返す
    s3.write_get_object_response(
        Body=json_bytes,
        RequestRoute=request_route,
        RequestToken=request_token,
        ContentType="application/json"
    )

    # handler の return はログ用でエラーにならないなら何でもOK
    return {"statusCode": 200}

以下のような点が注意である。

  • 変換後のオブジェクトをs3.write_get_object_responseで返す必要がある。これがないとAPIを叩いたときにエラーになる
    • この際にRequestRouteとRequestTokenが必要で、event["getObjectContext"]から取得できる。
    • 返すときはバイナリ
  • URLベースからアクセスしているが、ポリシーがなければエラーになるのでこのTerraformからは、S3バケットを外部公開しているわけではない

取得してみる

Object Lambdaのアクセスポイントから取得する必要がある。普通にS3のマネジメントコンソールからアクセスしたときに勝手に上書きしてくれる便利機能ではない。

ここからアクセスすると、以下のように名前をマスクされる(本来はここはBobになっている)

{"age": 25, "name": "***MASKED***"}

AWS CLIからaws cp ...で便利に取得できる機能はなく、プログラムベースで取得するときは、低レベルのAPIを使う必要がある。以下のようなコマンドでダウンロードできる。

aws s3api get-object --bucket arn:aws:s3-object-lambda:<リージョン>:<アカウントID>:accesspoint/my-object-lambda-ap --key object1.json output.json

結果はoutput.jsonに以下のように記録される。

{"age": 30, "name": "***MASKED***"}

所感

  • 思ったより地味な機能だなという印象
  • 似たようなアクセスポイントがあってハマった。権限も知らんかった。
  • S3で静的ページをホストしているときは便利かもしれない?


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です