[AWS CDK] Condition 付きの信頼ポリシーを設定した AssumeRole 用の IAM ロールを作成したい

2023.08.31

こんにちは、CX事業本部 Delivery部の若槻です。

今回は、AWS CDK で Condition 付きの信頼ポリシー(信頼関係)を設定した AssumeRole 用の IAM ロールを作成する方法を確認してみました。

理由としては、Amazon Managed Grafana ではコンソールからワークスペースを作成した際にサービスロールを自動作成できるのですが、これを CDK で Condition 付きで作成したかったためです。

はじめに結論

結論としては、次のように withConditions を使うことにより実現できます。

lib/cdk-sample-stack.ts

import { aws_iam, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  public readonly myFileObjectKey: string;

  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    const principal = new aws_iam.ServicePrincipal(
      'grafana.amazonaws.com'
    ).withConditions({
      StringEquals: {
        'aws:SourceAccount': this.account,
      },
      StringLike: {
        'aws:SourceArn': `arn:aws:grafana:${this.region}:${this.account}:/workspaces/*`,
      },
    });

    new aws_iam.Role(this, 'GrafanaRole', {
      assumedBy: principal,
    });
  }
}

上記により、次のような Condition 付きの信頼ポリシーを設定した AssumeRole 用の IAM ロールを作成できました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "grafana.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "XXXXXXXXXXXX"
                },
                "StringLike": {
                    "aws:SourceArn": "arn:aws:grafana:ap-northeast-1:XXXXXXXXXXXX:/workspaces/*"
                }
            }
        }
    ]
}

できなかった方法

その1

はじめに、次のように addStatements を使用して作成済みのロールに Condition 付きの PolicyStatement を追加しようとしました。

lib/cdk-sample-stack.ts

import { aws_iam, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  public readonly myFileObjectKey: string;

  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    const grafanaRole = new aws_iam.Role(this, 'GrafanaRole', {
      assumedBy: new aws_iam.ServicePrincipal('grafana.amazonaws.com'),
    });

    grafanaRole.assumeRolePolicy?.addStatements(
      new aws_iam.PolicyStatement({
        effect: aws_iam.Effect.ALLOW,
        actions: ['sts:AssumeRole'],
        conditions: {
          StringEquals: {
            'aws:SourceAccount': this.account,
          },
          StringLike: {
            'aws:SourceArn': `arn:aws:grafana:${this.region}:${this.account}:/workspaces/*`,
          },
        },
        principals: [new aws_iam.ServicePrincipal('grafana.amazonaws.com')],
      })
    );
  }
}

しかしこれだと、次のように Condition が設定された信頼ポリシーに加えて、assumedByに設定したプリンシパルの信頼ポリシーが追加されてしまいます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "grafana.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "grafana.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "XXXXXXXXXXXX"
                },
                "StringLike": {
                    "aws:SourceArn": "arn:aws:grafana:ap-northeast-1:XXXXXXXXXXXX:/workspaces/*"
                }
            }
        }
    ]
}

assumedBy は省略不可のため、ロール作成時に何かしらのプリンシパルを必ず指定する必要があります。

その2

続いて、次のような記述で設定をしようとしました。これは「IAM ポリシー」と「ロールの信頼ポリシー」を混同してしまっています。addToPolicyは IAM ポリシーを設定するためのものであり、ロールの信頼ポリシーを設定するためのものではありません。

lib/cdk-sample-stack.ts

import { aws_iam, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  public readonly myFileObjectKey: string;

  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    const grafanaRole = new aws_iam.Role(this, 'GrafanaRole', {
      assumedBy: new aws_iam.ServicePrincipal('grafana.amazonaws.com'),
    });
    const policy = new aws_iam.PolicyStatement({
      effect: aws_iam.Effect.ALLOW,
      actions: ['sts:AssumeRole'],
      conditions: {
        StringEquals: {
          'aws:SourceAccount': this.account,
        },
        StringLike: {
          'aws:SourceArn': `arn:aws:grafana:${this.region}:${this.account}:/workspaces/*`,
        },
      },
      principals: [new aws_iam.ServicePrincipal('grafana.amazonaws.com')],
    });
    grafanaRole.addToPolicy(policy);
  }
}

合成をしようとすると次のようにプリンシパルを指定できないと怒られます。

$ cdk synth
/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/cdk_sample_app/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:3
  `);throw new Error(`Validation failed with the following errors:
           ^
Error: Validation failed with the following errors:
  [CdkSampleStack/GrafanaRole/DefaultPolicy] A PolicyStatement used in an identity-based policy cannot specify any IAM principals.
  [CdkSampleStack/GrafanaRole/DefaultPolicy] A PolicyStatement used in an identity-based policy must specify at least one resource.

ロールの信頼ポリシーは、そのロールを「誰が使えるか」を定義するものです。一方で IAM ポリシーは、そのロール(ポリシー)を使って「何をできるか」を定義するものです。混同しないように気を付けましょう。

おわりに

AWS CDK で Condition 付きの信頼ポリシー(信頼関係)を設定した AssumeRole 用の IAM ロールを作成する方法を確認してみました。

ネット上に意外と情報が無かったので少し苦労しました。どなたかのお役に立てれば幸いです。

以上