API Gateway経由でStep Functionsのステートマシンを実行する

この記事は[BeeX Advent Calendar 2023](https://qiita.com/advent-calendar/2023/beex)の6日目の記事です。

# はじめに
AWSにはAPI機能をフルマネージドに提供するサービスとして、API Gatewayがあります。

よく見る構成としては、API Gatewayで作成されたAPI(RESTful等)に対してリクエストを送信し、受信したリクエストをバックエンドのLambdaで処理し結果を返す、というものがあります。

<br>

そのAPI Gatewayですが、Lambda等のリクエストを処理することができるコンピュートリソースをバックエンドに配置する以外に、直接AWSサービスのAPIをバックエンドとして呼び出すことも可能になっています。

今回はそのAPIを直接呼び出す仕組みを使用して、AWS Step Functionsの「StartExecution API」をAPI Gateway経由で呼び出す構成を検証していきます。
尚、API Gatewayを使用して「StartExecution API」を呼び出す手順は以下のドキュメントに記載されているので、こちらを参考に進めていきます。

https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-api-gateway.html

# 作業内容
## 前提
API Gatewayを作成する前に、実行されるステートマシンを事前に準備しておきます。
今回は実行できれば良いので、以前作成したPassステートを1つ持つステートマシンを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/3ca8a713-4524-c3df-5761-9b2194de77ee.png)

## １．IAMロール作成
ドキュメントの手順に従って、API Gatewayで利用するIAMロールを作成します。

- IAMロール：APIGatewayToStepFunctions 
- 信頼関係："apigateway.amazonaws.com"
- アタッチポリシー：AWSStepFunctionsFullAccess

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/e513e4f5-5e11-6474-3de1-e98e2af312e9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/26823065-cfd7-df56-100f-5d72c71f85ff.png)

## ２．API作成
API エンドポイントタイプで"リージョン"を選択したREST APIを新規に作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/80775f2a-08b9-91b1-413c-cd81280d071f.png)

"/execution"リソースを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/ef86bc8b-c912-e6dc-4bb8-dd64a1a41a24.png)

以下の内容で"POST"メソッドを作成します。

- メソッドタイプ：POST
- 統合タイプ：AWSのサービス
- AWSリージョン：ap-northeast-1
- AWSのサービス：Step Functions
- AWSサブドメイン：なし
- HTTPメソッド：POST
- アクションタイプ：アクション名を使用
- アクション名：StartExecution
- 実行ロール：手順１で作成したIAMロールのARN値
- 認証情報キャッシュ：発信者の認証情報をキャッシュキーに追加しない
- デフォルトタイムアウト：あり

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/1d547c9a-6333-95da-59d0-3e456287d784.png)

## ３．実行テスト
API Gatewayのテストメソッドを使用して、実行テストを行います。
以下のパラメータを指定します。"name"パラメータは敢えて指定していません。

```
{
   "input": "{}",
   "arn:aws:states:ap-northeast-1:123456789123:stateMachine:PassStateMachine"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/31535c69-0fd0-d719-14c5-dbb1bb210c62.png)

テストを実行すると、レスポンスが正常に返ってくることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/e66dff31-0cf9-7c94-550c-9d82672905f2.png)

念のためステートマシン側のコンソールも確認すると、実行された履歴が残っていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/e3375720-4769-1e43-9427-ea12268c12bc.png)

## ４．APIデプロイ
ここまでできたら、API GatewayのコンソールからAPIをデプロイします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/eb9ba9bb-d391-0f46-ba7b-0c5a61d2959a.png)

デプロイするとURLが確認できるようになるので、このURLに対してコマンドラインからAPIを実行してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/8812ba47-976e-efd8-64ed-e3f9e7f5cc42.png)

先ほどのテストと同じくレスポンスが返ってきていることが確認できました。

```
$ curl -X POST -d '{"input": "{}","stateMachineArn": "arn:aws:states:ap-northeast-1:123456789132:stateMachine:PassStateMachine"}' https://s8l41c37mk.execute-api.ap-northeast-1.amazonaws.com/dev/execution
{"executionArn":"arn:aws:states:ap-northeast-1:123456789132:execution:PassStateMachine:1fa77114-5aef-404f-921d-fbc84d922a7d","startDate":1.701750653715E9}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/7f7956f9-c3d5-9604-2363-e94ac0420923.png)

ステートマシンのコンソールを確認すると新しい実行履歴が追加されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/49727e4c-c2b6-f61d-34dc-8472c11ab796.png)

# おまけ
手順では、API Gateway作成時に設定するIAMロールに「AWSStepFunctionsFullAccess」ポリシーをアタッチしていましたが、実行するだけであればこれよりも少ない権限で実現できます。
具体的には以下の権限です。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "states:StartExecution",
            "Resource": "arn:aws:states:*:123456789132:stateMachine:*"
        }
    ]
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/42d37367-fbaf-5729-b07e-15d68fa3b36d.png)

また、そのままだとAPI URLが外部に漏れると自由に実行されてしまうため、WAFやリソースベースポリシーで実行元のソースIPを制限することができます。
以下はリソースベースポリシーで特定IPアドレス以外からの実行を拒否したときの例です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/fccb6a04-3240-b832-1fdd-0f6ed6f5bdc7.png)

実際に利用する場合は、上記などの観点も気にして設定するとよいと思います。

# おまけ2
ここまでで実装したAPI GatewayではStep Functionsのステートマシンを実行するだけでしたが、実行結果を取得するリソースも同じ手順で作成してみます。

「/execution/status」というパスでAPIに新しいリソースを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/0deddb49-58e9-cd7d-b596-915d4ef1c064.png)

「StartExecution」用メソッドを作成時と同じ内容で、アクション名だけを「DescribeExecution」に変更したメソッドを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/dd7b59f1-4ed0-cb0d-8767-1a78b93a0e5d.png)

以下を参考にCORSを有効化します。

https://qiita.com/kurata04/items/c5610b67cafec9b08198#iam-%E3%83%AD%E3%83%BC%E3%83%AB%E3%81%AB%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%E3%82%92%E3%82%A2%E3%82%BF%E3%83%83%E3%83%81%E3%81%99%E3%82%8B

- 「/execution」リソースのCORS有効化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/79107cbb-adb2-576f-56c6-5dcc02b00ec4.png)

- 「/execution/status」リソースのCORS有効化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/586f5b4f-7b53-102c-fd4d-ae882d037066.png)

API Gatewayのリソースベースポリシーも少し修正します。
具体的にはResource部分に追加したリソースを含めるように修正します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/9b203e75-53d6-1791-d76b-d9f420e6eddf.png)

APIをデプロイします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/349172c9-3a8d-e17e-b264-06e0e87c956c.png)

最後にAPI Gatewayに設定しているIAMロールのポリシーに「DescribeExecution」APIを許可する権限を追加します。(わかりやすさのためステートメントを分けています)

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "states:StartExecution",
            "Resource": "arn:aws:states:*:123456789123:stateMachine:*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "states:DescribeExecution",
            "Resource": [
                "arn:aws:states:*:123456789123:execution:*:*",
                "arn:aws:states:*:123456789123:express:*:*:*"
            ]
        }
    ]
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/8042ddff-8841-8e99-b4c5-e72f23d99991.png)

<br>

ここまでできたら実際にAPIを実行して想定通りに動作するか確認していきます。
まずコマンドラインからAPIにリクエストを投げて、ステートマシンを実行します。

```
$ curl -X POST -d '{"input": "{}","stateMachineArn": "arn:aws:states:ap-northeast-1:123456789123:stateMachine:PassStateMachine"}' https://s8l41c37mk.execute-api.ap-northeast-1.amazonaws.com/dev/execution
{"executionArn":"arn:aws:states:ap-northeast-1:123456789123:execution:PassStateMachine:e19c7aff-0985-4c4f-936f-4df104200a69","startDate":1.701837432549E9}
```

レスポンスとして、「executionArn」が返されるので、それを「DescribeExecution」APIのリクエストパラメータに含めてステータスチェックを行います。
「DescribeExecution」APIに必要なリクエストパラメータは以下に記載があります。

https://docs.aws.amazon.com/step-functions/latest/apireference/API_DescribeExecution.html

以下のコマンドを実行します。

```
$ curl -X POST -d '{"executionArn": "arn:aws:states:ap-northeast-1:123456789123:execution:PassStateMachine:e19c7aff-0985-4c4f-936f-4df104200a69"}' https://s8l41c37mk.execute-api.ap-northeast-1.amazonaws.com/dev/execution/status
```

レスポンス内容のstatus値を見ると成功していることが確認できました。

```
$ curl -X POST -d '{"executionArn": "arn:aws:states:ap-northeast-1:123456789123:execution:PassStateMachine:e19c7aff-0985-4c4f-936f-4df104200a69"}' https://s8l41c37mk.execute-api.ap-northeast-1.amazonaws.com/dev/execution/status
{"executionArn":"arn:aws:states:ap-northeast-1:123456789123:execution:PassStateMachine:e19c7aff-0985-4c4f-936f-4df104200a69","input":"{}","inputDetails":{"__type":"com.amazonaws.swf.base.model#CloudWatchEventsExecutionDataDetails","included":true},"name":"e19c7aff-0985-4c4f-936f-4df104200a69","output":"{}","outputDetails":{"__type":"com.amazonaws.swf.base.model#CloudWatchEventsExecutionDataDetails","included":true},"redriveCount":0,"redriveStatus":"NOT_REDRIVABLE","redriveStatusReason":"Execution is SUCCEEDED and cannot be redriven","startDate":1.701837432549E9,"stateMachineArn":"arn:aws:states:ap-northeast-1:123456789123:stateMachine:PassStateMachine","status":"SUCCEEDED","stopDate":1.701837432618E9,"traceHeader":"Root=1-656ffa78-6aeb8e79759bcdf2257765bc;Sampled=1"}
```

# おわりに
このようにAPI Gatewayを利用すると外部からAWSサービスのAPIを実行することができるようになります。