[demo] センサー情報処理:WAF + API Gateway + Lambda

AWSIaC

0. 概要

CloudFormationとLambda、API Gateway、WAFv2の連携に慣れるため、デモ環境を構築しました。

・WAFは開発用のため特定IP(自分)のみアクセス可能のルールを設定 
・API経由でセンサーデータ(速度や温度)を受信
・CloudWatch Logsに記録

※WAFv2(AWS::WAFv2::WebACLAssociation)は、API Gatewayに直接紐付けできないので、今後CloudFrontか、ALBを追加予定

参考記事:

ウェブ ACL と AWS リソースの関連付けまたは関連付け解除

関連付け可能なリソース
● リージョナル
・API Gateway REST API
・ALB
・AppSync GraphQL API
・Cognito ユーザープール
・App Runner
・AWS Verified Access インスタンス

● グローバル
・CloudFront


1. 構成

CloudFormationで以下のリソースを作成

工程AWSリソース
Lambda実行コードの配置S3
Lambda関数作成Lambda
APIエンドポイント作成API Gateway
IP制限ルールの設定WAFv2
Lambda実行ロールIAM Role

ネスト構成

  • stack-main.yaml:親テンプレート。S3上の子テンプレートを呼び出します
  • stack-api-lambda.yaml:子テンプレート。API Gateway、Lambda、WAFを定義

通信フロー

【通信イメージ】
[Client] → [WAFv2] → [API Gateway] → [Lambda Function] → [API Gateway] → [Client]

                                    [CloudWatch Logs]へログ出力

【通信フロー図】

        ┌────────────┐
        │   Client   │
        └────┬───────┘

HTTP POST リクエスト送信

        ┌────────────┐
        │   WAFv2    │
        └────┬───────┘

   ② リクエストを検査(IP許可制)

   ┌────────────────────┐
API Gateway      │
   └────────┬────────────┘

   ③ Lambdaにリクエストを転送

   ┌────────────────────┐
   │   Lambda Function   │
   └────────┬────────────┘

   ④ CloudWatch Logs にログ出力(非同期)

     ┌────────────────┐
     │ CloudWatchLogs │
     └────────────────┘



   ⑤ Lambdaが戻り値(レスポンス)をAPI Gatewayに返却

API GatewayがHTTPレスポンスとしてClientに返却

        ┌────────────┐
        │   Client   │
        └────────────┘



2. フォルダ構成

cfn-demo-api_lambda_waf/
├── lambda/                             # Lambda関数のソースコードとZIPファイル
│   ├── ingest_vehicle_sensor.py        # センサーデータ受信用Lambda関数(Python)
│   └── ingest_vehicle_sensor.zip       # 上記スクリプトをZIP化したデプロイ用ファイル
├── output.json                         # デプロイ後の出力情報(Outputsのログなど)
├── parameters/                         # CloudFormationスタックに渡すパラメータファイル置き場
│   └── dev-params.json                 # 開発環境用パラメータ(S3バケット名やIP制限など)
├── results/                            # 実行結果やログなどを出力する(現在は空ディレクトリ)
├── scripts/                            # スタック操作用のシェルスクリプト群
│   ├── check-status.sh                 # スタックの状態確認用スクリプト
│   ├── delete.sh                       # スタック削除スクリプト
│   ├── deploy-staging.sh               # ステージング環境用のデプロイスクリプト
│   └── deploy.sh                       # 開発環境用のデプロイスクリプト
├── templates/                          # CloudFormationテンプレート群
│   ├── stack-api-lambda.yaml           # 子テンプレート:Lambda + API Gateway + WAF 定義
│   └── stack-main.yaml                 # 親テンプレート:子テンプレートをネスト呼び出し
├── test-payload-utf8.json              # UTF-8形式のテストリクエストペイロード(API Gateway用)
└── test-payload.json                   # 標準形式のテストペイロード(POSTリクエスト用)

CloudFormationのコードはこちら

3. Lambda関数

## Lambda関数(要約)
def lambda_handler(event, context):
# JSONで受信されたセンサーデータをCloudWatchに記録
# 受信項目:sensor_id / timestamp / temperature / speedCode language: PHP (php)
Lamda関数(全体)
import json
import logging
from datetime import datetime, timezone

# CloudWatch Logs へ出力するためのロガー設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    """
    センサーから送信されたデータをAPI Gateway経由で受信し、
    その内容をCloudWatch Logsに記録するLambda関数。

    入力フォーマット想定(POSTリクエストのbody):
    {
        "sensor_id": "sensor-001",
        "timestamp": "2025-03-23T12:34:56Z",
        "temperature": 22.5,
        "speed": 60.2
    }
    """

    try:
        # API Gateway経由の場合、bodyは文字列として渡される
        body = event.get("body")
        if body is None:
            raise ValueError("Missing request body")

        # JSON文字列をPythonオブジェクトに変換
        payload = json.loads(body)

        # 必須項目の抽出(存在しない場合はデフォルトやNone)
        sensor_id = payload.get("sensor_id", "unknown")
        temperature = payload.get("temperature")
        speed = payload.get("speed")
        timestamp = payload.get("timestamp", datetime.now(timezone.utc).isoformat())

        # ログに出力する情報をまとめる
        log_data = {
            "sensor_id": sensor_id,
            "timestamp": timestamp,
            "temperature": temperature,
            "speed": speed,
            "source_ip": event.get("requestContext", {}).get("identity", {}).get("sourceIp", "N/A")
        }

        # CloudWatch Logs に情報を記録
        logger.info(f"Received sensor data: {json.dumps(log_data)}")

        # 正常応答を返す(API Gateway経由でクライアントに返される)
        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "Sensor data received successfully",
                "sensor_id": sensor_id
            })
        }

    except Exception as e:
        # エラーログを出力し、HTTP 400で応答
        logger.error(f"Error processing sensor data: {e}")
        return {
            "statusCode": 400,
            "body": json.dumps({"error": str(e)})
        }

4. パラメータ例(dev-params.json)

[
  { "ParameterKey": "BucketName", "ParameterValue": "cfn-demo-jin" },
  { "ParameterKey": "BucketPrefix", "ParameterValue": "cfn-demo-api_lambda_waf" },
  { "ParameterKey": "StageName", "ParameterValue": "dev" },
  { "ParameterKey": "AllowedIP", "ParameterValue": "xxx.xxx.xxx.xxx/32" }  ←開発用に自分のGlobalIPをWAFで許可
]Code language: JSON / JSON with Comments (json)

5. 補足

  • API Gatewayは POST /vehicle-data-demo_01 エンドポイントを提供
  • WAFは AllowedIP に指定されたIPのみ許可(それ以外はブロック)
  • LambdaコードはS3からデプロイ(事前にZIPファイルをアップロード)