Amazon EventBridge Scheduler を AWS CDK で構成する際に “Resolution error: All principals in a PolicyStatement must have the same Conditions” というエラーが発生する場合の対処

2024.01.12

こんにちは、CX 事業本部/製造ビジネステクノロジー部/新規サービスチームの若槻です。

Amazon EventBridge Scheduler は、Lambda 関数などのターゲットをスケジュール実行できる Amazon EventBridge の機能です。

以前までは EventBridge スケジュールルールでも同様のことができましたが、EventBridge Scheduler はスケジュール実行のための機能として特化しており、スケジュールルールに比べて高度なカスタマイズ性やスケーラビリティを備えています。両者の比較は次の記事が参考になります。

そして EventBridge Scheduler を AWS CDK で構成する場合は現状では alpha module を利用します。

この AWS CDK の alpha module を利用して Amazon EventBridge Scheduler を構成する際に、 Resolution error: All principals in a PolicyStatement must have the same Conditions というエラーが発生したので対処した内容を共有します。

はじめに結論

EventBridge Scheduler では次のように aws_scheduler_targets_alpha.LambdaInvoke を使用して Lambda 関数を実行ターゲットとして指定するのですが、その際に 同一の CDK スタック内で複数の Scheduler を作成する場合 は、ターゲットで明示的に role プロパティを指定する必要がありました。

lib/cdk-sample-stack.ts

import {
  aws_lambda_nodejs,
  aws_lambda,
  aws_iam,
  Stack,
  TimeZone,
} from 'aws-cdk-lib';
import * as aws_scheduler_alpha from '@aws-cdk/aws-scheduler-alpha';
import * as aws_scheduler_targets_alpha from '@aws-cdk/aws-scheduler-targets-alpha';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const accounId = this.account;
    const region = this.region;

    // Lambda 関数
    const helloFunc = new aws_lambda_nodejs.NodejsFunction(this, 'HelloFunc', {
      runtime: aws_lambda.Runtime.NODEJS_20_X,
      architecture: aws_lambda.Architecture.ARM_64,
    });

    // Lambda 実行ターゲット
    const target = new aws_scheduler_targets_alpha.LambdaInvoke(helloFunc, {
      role: new aws_iam.Role(this, 'Role', {
        assumedBy: new aws_iam.ServicePrincipal(
          'scheduler.amazonaws.com'
        ).withConditions({
          StringEquals: {
            'aws:SourceAccount': accounId,
          },
          StringLike: {
            'aws:SourceArn': `arn:aws:scheduler:${region}:${accounId}:schedule/default/*`,
          },
        }),
      }),
    });

    // Scheduler その1
    new aws_scheduler_alpha.Schedule(this, 'Shedule1', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '3',
        day: '1-31',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target,
    });

    // Scheduler その2
    new aws_scheduler_alpha.Schedule(this, 'Shedule2', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '4',
        day: '1-10',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target,
    });
  }
}

CDK デプロイにより、上記で定義した 2 つの Scheduler が作成されました。

事象

当初、同じ Lambda 関数をターゲットとした複数の Scheduler を次のように作成しようとしました。

lib/cdk-sample-stack.ts

import { aws_lambda_nodejs, aws_lambda, Stack, TimeZone } from 'aws-cdk-lib';
import * as aws_scheduler_alpha from '@aws-cdk/aws-scheduler-alpha';
import * as aws_scheduler_targets_alpha from '@aws-cdk/aws-scheduler-targets-alpha';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Lambda 関数
    const helloFunc = new aws_lambda_nodejs.NodejsFunction(this, 'HelloFunc', {
      runtime: aws_lambda.Runtime.NODEJS_20_X,
      architecture: aws_lambda.Architecture.ARM_64,
    });

    // Lambda 実行ターゲット
    const target = new aws_scheduler_targets_alpha.LambdaInvoke(helloFunc, {});

    // Scheduler その1
    new aws_scheduler_alpha.Schedule(this, 'Shedule1', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '3',
        day: '1-31',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target,
    });

    // Scheduler その2
    new aws_scheduler_alpha.Schedule(this, 'Shedule2', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '4',
        day: '1-10',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target,
    });
  }
}

しかし CDK Synth 時に次のようなエラーが発生しました。

$ cdk synth

Error: Resolution error: Resolution error: Resolution error: All principals in a PolicyStatement must have the same Conditions (got '{}' and '{"StringEquals":{"aws:SourceAccount":"${Token[AWS.AccountId.9]}"}}'). Use multiple statements instead..

切り分け

その1

実行対象の Lambda 関数およびターゲットを Scheduler ごとに分けてみました。

lib/cdk-sample-stack.ts

import { aws_lambda_nodejs, aws_lambda, Stack, TimeZone } from 'aws-cdk-lib';
import * as aws_scheduler_alpha from '@aws-cdk/aws-scheduler-alpha';
import * as aws_scheduler_targets_alpha from '@aws-cdk/aws-scheduler-targets-alpha';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Lambda 関数1
    const helloFunc = new aws_lambda_nodejs.NodejsFunction(this, 'HelloFunc', {
      runtime: aws_lambda.Runtime.NODEJS_20_X,
      architecture: aws_lambda.Architecture.ARM_64,
    });

    // Lambda 実行ターゲット1
    const target = new aws_scheduler_targets_alpha.LambdaInvoke(helloFunc, {});

    // Scheduler その1
    new aws_scheduler_alpha.Schedule(this, 'Shedule1', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '3',
        day: '1-31',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target,
    });

    // Lambda 関数2
    const helloFunc2 = new aws_lambda_nodejs.NodejsFunction(
      this,
      'HelloFunc2',
      {
        runtime: aws_lambda.Runtime.NODEJS_20_X,
        architecture: aws_lambda.Architecture.ARM_64,
      }
    );

    // Lambda 実行ターゲット2
    const target2 = new aws_scheduler_targets_alpha.LambdaInvoke(
      helloFunc2,
      {}
    );

    // Scheduler その2
    new aws_scheduler_alpha.Schedule(this, 'Shedule2', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '4',
        day: '1-10',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target: target2,
    });
  }
}

すると同じエラーが再度発生しました。

$ cdk synth

Error: Resolution error: Resolution error: Resolution error: All principals in a PolicyStatement must have the same Conditions (got '{}' and '{"StringEquals":{"aws:SourceAccount":"${Token[AWS.AccountId.9]}"}}'). Use multiple statements instead..

これ、同一スタック内で同じロールを複数の Scheduler 間で共用しようとしてエラーになっているのでしょうか?

その2

次のように 1 つの Scheduler を構成しようとした場合は、もちろんデプロイ含め成功します。

lib/cdk-sample-stack.ts

import { aws_lambda_nodejs, aws_lambda, Stack, TimeZone } from 'aws-cdk-lib';
import * as aws_scheduler_alpha from '@aws-cdk/aws-scheduler-alpha';
import * as aws_scheduler_targets_alpha from '@aws-cdk/aws-scheduler-targets-alpha';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Lambda 関数
    const helloFunc = new aws_lambda_nodejs.NodejsFunction(this, 'HelloFunc', {
      runtime: aws_lambda.Runtime.NODEJS_20_X,
      architecture: aws_lambda.Architecture.ARM_64,
    });

    // Lambda 実行ターゲット
    const target = new aws_scheduler_targets_alpha.LambdaInvoke(helloFunc, {});

    // Scheduler その1
    new aws_scheduler_alpha.Schedule(this, 'Shedule1', {
      schedule: aws_scheduler_alpha.ScheduleExpression.cron({
        minute: '0',
        hour: '1',
        month: '3',
        day: '1-31',
        year: '2024',
        timeZone: TimeZone.ASIA_TOKYO,
      }),
      target,
    });
  }
}

そして作成された SchedulerRoleForTarget の Trust Relationship は次のようになっていました。

$ aws iam get-role --role-name $SchedulerRoleForTargetName --query 'Role.AssumeRolePolicyDocument'
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "scheduler.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "XXXXXXXXXXXX"
                }
            }
        }
    ]
}

上記のステートメントの Condition の記述は、

(got '{}' and '{"StringEquals":{"aws:SourceAccount":"${Token[AWS.AccountId.9]}"}}')

とあるうちの後者 {"StringEquals":{"aws:SourceAccount":"${Token[AWS.AccountId.9]}"}} に該当します。そして事象が発生したパターンでは上記に加えて 2 つ目の {} が追加されようとして、両者が異なっているためエラーとなったようです。

そこで role プロパティを明示的に指定することにより、冒頭に示した通りエラーを回避できました。

おわりに

AWS CDK の alpha module を利用して Amazon EventBridge Scheduler を構成する際に、 Resolution error: All principals in a PolicyStatement must have the same Conditions というエラーが発生したので対処した内容を共有しました。

バグのような挙動なので、今後修正される可能性もありますが、現時点では上記のような対処が必要です。注意しつつ活用していきましょう。

参考

以上