[AWS Step Functions] AWS Service API 呼び出しに使用する CallAwsService で、自動的に追加されるパーミッションについて確認してみました

2023.03.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

CX 事業本部のデリバリー部の平内(SIN)です。

AWS Step Functions(以下、Step Functions)では、ワークフローから 200 を超える AWS のサービス(9,000 を超えるアクション)を直接呼び出すことができます。
その他の AWS のサービスを呼び出す

そして、AWS CDK(以下、CDK) では、そのためのコンストラクタとしてCallAwsServiceが用意されています。
class CallAwsService (construct)

CallAwsServiceでは、コールする API に必要なパーミッショッも併せて生成してくれますが、今回は、その内容について確認してみました。

2 自動的に生成されるポリシー

S3 バケットから、get-objectで、ファイルをダウンロードするとしたら、以下のようなコードになります。

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_stepfunctions, aws_stepfunctions_tasks } from "aws-cdk-lib";

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucketName = "work-2023-02-24";
    const keyName = "sample.txt";

    const getObject = new aws_stepfunctions_tasks.CallAwsService(
      this,
      "GetObject",
      {
        service: "s3",
        action: "getObject",
        parameters: {
          Bucket: bucketName,
          Key: keyName,
        },
        iamResources: [`arn:aws:s3:::${bucketName}/*`],
      }
    );

    new aws_stepfunctions.StateMachine(this, "StateMachine", {
      stateMachineName: "myStateMachine",
      definition: getObject,
    });
  }
}

上記を使用して作成された Step Functions を、AWS コンソールで見た様子です。

そして、ロールを確認してみると次のようになっています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "s3:getObject",
      "Resource": "arn:aws:s3:::work-2023-02-24/*",
      "Effect": "Allow"
    }
  ]
}

こちらは、CallAwsServiceの必須パラメータである、 serviceactioniamResourcesから生成されています。

3 アクション名が、ポリシーと一致しない場合 (iamAction)

生成されるポリシーは、serviceactionの組み合わせで決まりますが、その組み合わせで対応できない場合もあります。

例)ListObjectsV2 に必要なポリシーは、s3:ListBucket
参考:ListObjects なんていう S3 のアクションは存在しない

残念ながら、以下のコードで生成される StepFunctions は Access Denied となります。

const listObjectsV2 = new aws_stepfunctions_tasks.CallAwsService(
  this,
  "ListObjectsV2",
  {
    service: "s3",
    action: "listObjectsV2",
    parameters: {
      Bucket: bucketName,
    },
    iamResources: [`arn:aws:s3:::${bucketName}`],
  }
);

この原因は、生成されたポリシーが以下のようになっているためです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "s3:listObjectsV2",
      "Resource": "arn:aws:s3:::work-2023-02-24",
      "Effect": "Allow"
    }
  ]
}

対策としては、パラメータとしてのiamActionを追加します。

const listObjectsV2 = new aws_stepfunctions_tasks.CallAwsService(
  this,
  "ListObjectsV2",
  {
    service: "s3",
    action: "listObjectsV2",
    parameters: {
      Bucket: bucketName,
    },
    iamResources: [`arn:aws:s3:::${bucketName}`],
    iamAction: "s3:ListBucket", // <= この行を追加
  }
);

iamActionが指定されると、生成されるポリシーは、iamActioniamResourcesの組み合わせとなります。

Access Deniedが解消されていることが確認できます。

4 追加のポリシーが必要な場合(additionalIamStatements)

actionserviceiamActionで対応できない場合、additionalIamStatementsを使用します。

例として・・・

rekognition:detectTextを使用すると、画像の中からテキストを検出することができます。

バケットに桜の画像を配置します。

以下のコードで StepFunctions を構成します。

const bucketName = "work-2023-02-24";
const keyName = "sample.jpg";

const detectText = new aws_stepfunctions_tasks.CallAwsService(
  this,
  "DetectText",
  {
    service: "rekognition",
    action: "detectText",
    iamResources: ["*"],
    parameters: {
      Image: {
        S3Object: {
          Bucket: bucketName,
          Name: keyName,
        },
      },
    },
    additionalIamStatements: [
      new aws_iam.PolicyStatement({
        actions: ["s3:getObject"],
        resources: [`arn:aws:s3:::${bucketName}/*`],
      }),
    ],
  }
);

実行すると、DetectedText に、Cherry blossomsが検出されていることが確認できます。

rekognition:detectTextでは、S3 バケットにアクセスするための追加のポリシーが必要ですが、これを追加しているのが、additionalIamStatementsです。

先のコードで生成されたポリシーは、以下のようになっています。

action及び、serviceの組み合わせに追加して、additionalIamStatementsに列挙したポリシーも追加されています。

additionalIamStatementsを追加しなかった場合は、S3 へアクセスできなかったことが原因で失敗します。

5 参考: GUI からの操作では、ポリシー追加はされない

AWS コンソールからステートマシンを作成する場合、GUI から簡単に操作できます。

しかし、この場合、AWS サービス API を使用するためのパーミッションは、別途設定する必要があります。

代わりに、xray に関するパーミッションが追加されていることが確認できます。

6 参考: CustomState では、ポリシー追加はされない

パラメータを全て JSON で設定できる、低レベルのコンストラクタであるCustomStateでも、同じように記述できますが、こちらは、ポリシーを自動的に追加することはありません。
class CustomState (construct)

const getObject = new aws_stepfunctions.CustomState(this, "GetObject", {
  stateJson: {
    Type: "Task",
    Resource: "arn:aws:states:::aws-sdk:s3:getObject";,
    Parameters: {
      Bucket: bucketName,
      Key: keyName,
    },
  },
});

AWS サービス API を使用する場合、現時点で、CustomStateが必要になることはないと思います。

7 最後に

今回は、CallAwsService コンストラクで生成されるパーミションについて確認してみました。

複雑な処理になると、まだまだ Lambda での実装に軍配が上がりそうですが、2,3 の API サービスの組み合わせだけで要件が完了してしまうような場合、CallAwsServiceで簡単に書けることの魅力は捨て難いですよね。

[感謝] 桜の画像は、Pixabayのものを利用させて頂きました。 https://pixabay.com/ja/photos/%e3%83%94%e3%83%b3%e3%82%af-%e6%a1%9c-%e3%83%95%e3%83%a9%e3%83%af%e3%83%bc%e3%82%ba-%e6%94%af%e5%ba%97-324175/