未認証ユーザー自動削除機能の実装手順: Lambda と EventBridge を活用した自動化

#### **概要**

未認証ユーザーの自動削除を実現するため、AWS Lambda と EventBridge を活用して定期的に RDS データベースから未認証ユーザーを削除する仕組みを構築しました。

この記事では技術スタック、全体の手順、作業の流れを整理し、特につまずいたポイントを詳細に説明します。


とっかかりは下記の記事を参照しています。

https://qiita.com/curlneko/items/3dba8f98109257dbbea6

---

### **技術スタック**

- **AWS Lambda**: サーバーレス環境でコードを実行。
- **Amazon RDS**: ユーザーデータを格納するデータベース。
- **Secrets Manager**: データベース認証情報を安全に管理。
- **EventBridge**: 定期実行をスケジュール。
- **VPC & セキュリティグループ**: Lambda と RDS 間の安全な通信を実現。
- **Node.js**: Lambda 関数の実装言語。

---

### **作業の流れ**

#### 1. **Secrets Manager にシークレットを作成**
   - **手順**: RDS の認証情報 (username, password) を保存。 
   - **ポイント**: Secrets Manager に保存した ARN をメモしておく。
   - **例**:
     ```json
     {
       "username": "admin",
       "password": "yourpassword",
       "host": "your-db-host.amazonaws.com",
       "dbname": "yourdbname"
     }
     ```

#### 2. **Lambda 関数を作成**
   - **コード例**:
     ```javascript
     const AWS = require('aws-sdk');
     const mysql = require('mysql2/promise');

     const getDatabaseCredentials = async () => {
       const secretsManager = new AWS.SecretsManager({ region: 'ap-northeast-1' });
       const secretValue = await secretsManager.getSecretValue({ SecretId: 'your-secret-id' }).promise();
       return JSON.parse(secretValue.SecretString);
     };

     exports.handler = async () => {
       try {
         const credentials = await getDatabaseCredentials();
         const { username, password, host, dbname } = credentials;
         const connection = await mysql.createConnection({
           host,
           user: username,
           password,
           database: dbname,
           port: 3306,
         });

         const query = `
           DELETE FROM users 
           WHERE confirmed_at IS NULL 
           AND confirmation_sent_at < NOW() - INTERVAL 1 DAY;
         `;
         const [result] = await connection.execute(query);
         console.log(`${result.affectedRows} unconfirmed users deleted.`);
         await connection.end();
       } catch (err) {
         console.error('Error:', err);
       }
     };
     ```

#### 3. **Lambda に IAM ロールをアタッチ**
   - **必要なポリシー**:
     ```json
     {
       "Effect": "Allow",
       "Action": "secretsmanager:GetSecretValue",
       "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:your-secret-id"
     }
     ```

#### 4. **VPC とセキュリティグループの設定**
   - Lambda 関数が VPC 内の RDS にアクセスできるよう、以下を設定。
     - **VPC エンドポイント**: Secrets Manager へのアクセスを可能にする。
     - **セキュリティグループ**:
       - Lambda と RDS 間の通信 (ポート 3306) を許可。
       - Lambda 同士の HTTPS 通信 (ポート 443) を許可。

#### 5. **EventBridge でスケジュール設定**
   - **手順**: EventBridge ルールを作成して、`cron(0 16 * * ? *)` 形式で毎日実行をスケジュール。
   - Lambda 関数にトリガーとして EventBridge を紐付け。

#### 6. **テスト実行とログ監視**
   - CloudWatch Logs を活用して、エラーがないことを確認。

---

### **つまずいたポイントと解決策**

#### **1. Secrets Manager の ARN 問題**
- **現象**: Lambda 関数が `AccessDeniedException` を出力。シークレット ARN に `-V3ptOS` が付与されている場合とそうでない場合で不整合が発生。
- **原因**: シークレット ARN を完全指定する必要があることに気付かず、トリミングされた ARN を利用。
- **解決策**: `-V3ptOS` を含めた完全な ARN をポリシーに指定。

#### **2. VPC エンドポイントの設定漏れ**
- **現象**: Lambda から Secrets Manager への接続がタイムアウト。
- **原因**: VPC 内の Lambda がパブリックインターネット経由で Secrets Manager にアクセスしようとして失敗。
- **解決策**: VPC エンドポイントを作成し、プライベートネットワーク内で通信。

#### **3. セキュリティグループの設定不足**
- **現象**: RDS への接続エラー (`ECONNREFUSED`)。
- **原因**: Lambda に紐付けたセキュリティグループが RDS のセキュリティグループに通信を許可していなかった。
- **解決策**:
  - Lambda のセキュリティグループに、RDS のセキュリティグループへの MySQL ポート (3306) 通信を許可。

#### **4. トリガーのスケジュール式のミス**
- **現象**: `Parameter ScheduleExpression is not valid` エラー。
- **原因**: `cron` 式の形式ミス。
- **解決策**: AWS ドキュメントに従い正しい形式 (`cron(0 16 * * ? *)`) を適用。

---

### **まとめ**

本プロジェクトでは、未認証ユーザーの自動削除という一見シンプルなタスクにおいて、Secrets Manager、VPC エンドポイント、セキュリティグループ、IAM ポリシーといった AWS の複数のサービスを正しく設定する必要がありました。

特に以下の点が重要です:
1. **Secrets Manager ARN の正確な指定**
2. **VPC エンドポイントを用いたプライベート通信**
3. **セキュリティグループのルール設定の見直し**

これらの問題を一つずつ解決し、無事にスケジュールされた Lambda 関数の運用が可能となりました。同様の実装を行う方は、設定の細部を丁寧に確認しながら進めてください。

---